/******************************************************************************************************
Copyright (C) 2018 Mestrelab Research S.L. All rights reserved.

This file is part of the Mnova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*******************************************************************************************************/

/*globals dialog, mainWindow, molecule, qsTr, serialization, settings, Application, BinaryStream, BMRB_Engine, Emolecules_Engine, Env, MoleculeSearcher, MoleculeSearcherViewer, Dir, File, FileDialog, MessageBox, Molecule, MnUi, Pixmap,  TextStream, Timer, WebUtils, WebUtilsQT*/

/*jslint plusplus: true, indent: 4, continue:true, regexp: true */

function ManageStructureData() {
	'use strict';

	ManageStructureData.prototype.parseCommonOptions = function (aXml, aOptions) {
		aXml = aXml.replace("_Complexity_", aOptions.complexity);
		aXml = aXml.replace("_Isotopic_", aOptions.isotopic);
		aXml = aXml.replace("_HasSpectra_", aOptions.hasSpectra);
		aXml = aXml.replace("_HasPatents_", aOptions.hasPatents);
		aXml = aXml.replace("_MatchType_", aOptions.matchType);
		aXml = aXml.replace("_Calc3D_", aOptions.calc3D);

		return aXml;
	};

	ManageStructureData.prototype.getElementsFromXML = function (aXml, aLabel) {

		var elementArray, xml, searchStringBegin, searchStringEnd, begin, end, element, labelLenght;

		elementArray = [];
		xml = aXml;
		searchStringBegin = "<" + aLabel + ">";
		searchStringEnd = "</" + aLabel + ">";
		labelLenght = searchStringBegin.length;

		while (xml.length && xml.indexOf(searchStringBegin) !== "-1") {

			begin = xml.indexOf(searchStringBegin);
			if (begin !== -1) {
				begin = begin + labelLenght;
			}
			end = xml.indexOf(searchStringEnd);

			element = xml.slice(begin, end);
			element = element.replace("&lt;", "<");
			element = element.replace("&gt;", ">");
			element = element.replace(new RegExp("&amp;", 'g'), "&");

			if (element) {
				elementArray.push(element);
			}

			xml = xml.substring(xml.indexOf(searchStringEnd) + searchStringEnd.length);
		}
		return elementArray;
	};

	ManageStructureData.prototype.getElementsFromJson = function (aJson, aKey) {

		var elementArray = [],
			myJson = aJson,
			i;

		for (i = 0; i < myJson.length; i++) {
			elementArray.push(myJson[i][aKey]);
		}
		return elementArray;
	};

	ManageStructureData.prototype.getElementsFromText = function (aText) {

		var elementArray = [];

		elementArray.push(aText);
		return elementArray;
	};

	ManageStructureData.prototype.getElementsFromJsonKey = function (aJson, aKey) {

		return aJson.hasOwnProperty(aKey) ? aJson[aKey] : aJson;
	};
}

//***********Static Methods***************
ManageStructureData.createStructureFromTableWidgetFields = function (aObject, aListOfFields, aExtraFields) {
	"use strict";

	var i, fields, field;

	aObject = {};
	fields = aListOfFields;

	for (i = 0; i < fields.length; i++) {
		field = fields[i];
		aObject[field] = [];
	}

	if (aExtraFields !== undefined && aExtraFields.length > 0) {
		fields = aExtraFields;
		for (i = 0; i < fields.length; i++) {
			field = fields[i];
			aObject[field] = [];
		}
	}

	return aObject;
};

function MoleculeSearcherViewer() {
	'use strict';

	//Load UIs and initalize main objects
	this.dockWidget = {};
	this.dockWidget = Application.loadUiFile('ricares:MoleculeSearcher.ui');
	this.dockWidget.show();
	this.optionsDialog = {};
	this.optionsDialog = Application.loadUiFile('ricares:MoleculeSearcherOptions.ui');
	this.optionsDialog.close();
	this.lastMolFileStructureSearch = "";
	this.lastMolFileStructureSearchLoaded = "";
	this.dockWidget.widgets.progressBar.visible = false;
	this.dockWidget.widgets.toolButtonCancel.visible = false;
	this.revisionWebUtilsQT = 16393;
	this.heightOnlyMoleculesFeature = 384;
	this.defaultMaximumHits = this.optionsDialog.widgets.spinboxMaximumHits.value;
	this.deselectedByClickingLoadActiveMolecule = false;
	this.fitMoleculeImage = true;

	//Structure date to store molecule information
	//This field and the below one, must match with the name of each tableWidget column.
	//Always must be synchronized with the list obtained with aTableWidget.horzHeaderItems (in English)
	this.molecule = {};
	this.molecule.fields = ["", "Database", "Available Spectra", "Molecular Formula", "InChI", "SMILES", "Average Mass", "Molecular Weight", "Monoisotopic Mass"];
	this.molecule.extraFields = ["ID", "Spectra", "ENC_ID"];
	this.molecule.tableWidget = this.dockWidget.widgets.tableWidgetMoleculesResult;
	this.molecule.tbCheckAll = this.dockWidget.widgets.toolButtonCheckAllMolecules;
	this.molecule.tbUncheckAll = this.dockWidget.widgets.toolButtonUncheckAllMolecules;
	this.molecule.tbImport = this.dockWidget.widgets.toolButtonGetSelectedMolecules;
	this.molecule.tbDownload = this.dockWidget.widgets.toolButtonDownloadSelectedMolecules;
	this.molecule.tbActiveLoad = this.dockWidget.widgets.toolButtonLoadActiveMolecule;
	this.molecule.tbTextSearch = this.dockWidget.widgets.toolButtonSearch;
	this.molecule.tbActiveResultImport = this.dockWidget.widgets.toolButtonGetResultActiveMolecule;
	this.molecule.tbStructureSearch = this.dockWidget.widgets.toolButtonStructureSearch;
	this.molecule.tbSubstructureSearch = this.dockWidget.widgets.toolButtonSubstructureSearch;
	this.molecule.leTextInput = this.dockWidget.widgets.tbInputMolecule;
	this.molecule.statusBar = this.dockWidget.widgets.labelStatusInfoData;
	this.molecule.ID = "Molecules";
	this.molecule.statusFieldNumber = 0;
	this.molecule.engineFieldNumber = 1;
	this.molecule.totalRowsChecked = 0;
	this.molecule.rowClicked = -1;
	this.molecule.lastRowClicked = -1;
	this.molecule.flagCellChanged = false;
	this.molecule.flagCellDoubleClicked = false;
	this.molecule.flagCurrentCellChanged  = false;
	this.molecule.flagImport = false;
	this.molecule.flagDownload = false;
	this.molecule.flagSearch = false;
	this.molecule.flagCompoundsTable = false;
	this.molecule.flagTimerCompundsTable = false;
	this.molecule.flagImage = false;
	this.molecule.result = ManageStructureData.createStructureFromTableWidgetFields(this.molecule.result, this.molecule.fields, this.molecule.extraFields);
	this.molecule.compoundsTable = mainWindow.getObject("CompoundTable");
	this.molecule.compoundTablePixmap = undefined;
	this.molecule.timerCompoundsTable = new Timer();
	this.molecule.timerCompoundsTable.start(500);

	//Structure data to store spectra information
	//This field and the below one, must match with the name of each tableWidget column.
	//Always must be synchronized with the list obtained with aTableWidget.horzHeaderItems (in English)
	this.spectra = {};
	this.spectra.fields = ["", "Database", "Type", "Submitted Date", "Filename", "Comments"];
	this.spectra.extraFields = ["ID"];
	this.spectra.tableWidget = this.dockWidget.widgets.tableWidgetSpectraResult;
	this.spectra.tbCheckAll = this.dockWidget.widgets.toolButtonCheckAllSpectra;
	this.spectra.tbUncheckAll = this.dockWidget.widgets.toolButtonUncheckAllSpectra;
	this.spectra.tbImport = this.dockWidget.widgets.toolButtonGetSelectedSpectra;
	this.spectra.tbDownload = this.dockWidget.widgets.toolButtonDownloadSelectedSpectra;
	this.spectra.statusBar = this.dockWidget.widgets.labelStatusInfoData;
	this.spectra.ID = "Spectra";
	this.spectra.statusFieldNumber = 0;
	this.spectra.engineFieldNumber = 1;
	this.spectra.totalRowsChecked = 0;
	this.spectra.rowClicked = -1;
	this.spectra.semaphoreSearch = false;
	this.spectra.flagImport = false;
	this.spectra.flagDownload = false;
	this.spectra.flagCellChanged = false;
	this.spectra.flagSearch = false;
	this.spectra.result = ManageStructureData.createStructureFromTableWidgetFields(this.spectra.result, this.spectra.fields, this.spectra.extraFields);

	//Structure date to store the instaces of the available engines
	this.availableEngines = MoleculeSearcher.getAvailableEngines(MoleculeSearcher.engineIDs);

	//Structure data to store the engine activation process
	this.dockWidget.widgets.toolButtonBMRB.engineID = BMRB_Engine.ID;
	this.dockWidget.widgets.toolButtoneMolecules.engineID = Emolecules_Engine.ID;

	//Structure date to store the GUI options.
	//The name of each component GUI goes just before the underscore.
	//The nomenclature for requiredAction property is as follows: actionToBePerformed_tokenUsedInTheAction. Current avialable actions are: {getBefore, remove} check methos this.applyRequiredActions
	this.options = {};
	this.options.spinboxMaximumHits = this.optionsDialog.widgets.spinboxMaximumHits;
	this.options.flagButtonBox = false;

	//Create a WebUtils instance by checking the revision of the version in order to use the new library when it makes sense.
	if (Application.version.revision >= this.revisionWebUtilsQT) {
		this.webUtils = new WebUtilsQT();
	} else {
		this.webUtils = new WebUtils();
		if (!this.webUtils.isValid) {
			MessageBox.critical(qsTr("Error initializing WebUtils"));
			return;
		}
	}

	//Registry Entries
	var outputDirKey = "Molecule Searcher/Output Directory",
		maximumHitsKey = "Molecule Searcher/Maximum Hits",
		lastOptionTabIndex = "Molecule Searcher/Last Option Tab Index";

	MoleculeSearcherViewer.prototype.clearLoadedStructureSearch = function () {

		this.molecule.tbActiveLoad.enabled = false;
		this.lastMolFileStructureSearch = "";
		this.lastAtomsNumberStructureSearchLoaded = undefined;
		this.lastBondNumberStructureSearchLoaded = undefined;
		this.molecule.compoundTablePixmap = undefined;
	};

	MoleculeSearcherViewer.prototype.loadActiveMoleculeForStructureSearch = function (aMolecule) {

		var mol;

		this.molecule.tbActiveLoad.enabled = true;
		mol = Application.molecule.activeMolecule();
		if (aMolecule !== undefined && aMolecule) {
			mol = aMolecule;
		}
		this.dockWidget.widgets.tabWidget.setCurrentIndex(0);
		this.lastMolFileStructureSearch = mol.getMolfile();
		this.lastAtomsNumberStructureSearch = mol.atomCount;
		this.lastBondNumberStructureSearch = mol.bondCount;
		this.molecule.compoundTablePixmap = mol.toPixmap();
	};

	MoleculeSearcherViewer.prototype.thereIsMoleculePageItemSelected = function (aOnlyOne) {

		var i, document, selectedItems, item, result;

		result = false;
		document = mainWindow.activeDocument;
		selectedItems = document.selection();
		for (i = 0; i < selectedItems.length; i++) {
			item = selectedItems[i];
			if (item.name === "Molecule") {
				if (aOnlyOne !== undefined && aOnlyOne) {
					if (result) {
						result = false;
						break;
					} else {
						result = true;
					}
				} else {
					result = true;
					break;
				}
			}
		}

		return result;
	};

	MoleculeSearcherViewer.prototype.loadInitialMolecule = function () {

		var selectedMol, compoundTableState, flagMolPageItemSelected, item, mol;

		selectedMol = molecule.activeMolecule();
		if (selectedMol !== undefined && selectedMol.uuid !== undefined && selectedMol.name === "Molecule") {
			compoundTableState = mainWindow.activeDocument.selectedMolTableItems();

			//Get active molecule if there is not selection in compound table
			if (compoundTableState.length === 0) {
				this.loadActiveMoleculeForStructureSearch(selectedMol);

			//We have a row selected in compounds table
			} else if (compoundTableState.length === 1) {
				flagMolPageItemSelected = this.thereIsMoleculePageItemSelected();

				//If there is a molecule page items selected and a row in compound table we disable the feature
				if (flagMolPageItemSelected) {
					this.clearLoadedStructureSearch();

				//If there is only a compound table item selected we get it
				} else {
					item = compoundTableState[0];
					if (item.name === "Molecule") {
						mol = new Molecule(item);
						if (item.isValid()) {
							this.loadActiveMoleculeForStructureSearch(mol);
						}
					}
				}

			//If there are many items selected in compound table we disable the structure load option
			} else {
				this.clearLoadedStructureSearch();
			}
		}
	};

	MoleculeSearcherViewer.prototype.synchronizeOptionsObjectWithGUI = function (aGUIObject) {

		var i, engine, featuresGroups, currentFeatureGroup, featuresGroup, currentFeature, currentAction, actions, auxObject, message;

		//Iterate throw engines
		try {
			for (i = 0; i < this.availableEngines.length; i++) {
				engine = this.availableEngines[i];
				featuresGroups = engine.engine.options;

				//Iterate throw group of features of each engine (i.e. textSearch, substructure search, etc.)
				for (currentFeatureGroup in featuresGroups) {
					if (featuresGroups.hasOwnProperty(currentFeatureGroup)) {
						featuresGroup = engine.engine.options[currentFeatureGroup];

						//Iterate throw a specific feature (i.e. hasSpectra, complexity, etc.)
						for (currentFeature in featuresGroup) {
							if (featuresGroup.hasOwnProperty(currentFeature)) {
								actions = engine.engine.options[currentFeatureGroup][currentFeature];

								//Backup previous objects if any
								auxObject = {};
								for (currentAction in actions) {
									if (actions.hasOwnProperty(currentAction)) {
										auxObject[currentAction] = engine.engine.options[currentFeatureGroup][currentFeature][currentAction];
									}
								}

								//Overwrite object with GUI object
								engine.engine.options[currentFeatureGroup][currentFeature] = aGUIObject.widgets[currentFeature];

								//Restore possible previous objects
								for (currentAction in auxObject) {
									if (auxObject.hasOwnProperty(currentAction)) {
										engine.engine.options[currentFeatureGroup][currentFeature][currentAction] = auxObject[currentAction];
									}
								}
							}
						}
					}
				}
			}
		} catch (e) {
			message = "<font color=red>{0}</font>".format(qsTr("An exception has occurred loading the options"));
		} finally {
			this.finalizeProgressBar(message);
		}
	};

	MoleculeSearcherViewer.prototype.engineActivationManagement = function (aEngineID) {

		var i, engine, flagActive, flagChange;

		//We abort if there is an active process still running
		if (this.activeProcess()) {
			return;
		}

		//Check if we have at least a engine active
		flagActive = false;
		for (i = 0; i < this.availableEngines.length && !flagActive; i++) {
			engine = this.availableEngines[i];
			if (engine.id !== aEngineID) {
				if (engine.active) {
					flagActive = true;
				}
			}
		}

		//Get the current engine
		flagChange = true;
		engine = MoleculeSearcher.getEngineInstance(aEngineID, this.availableEngines);

		//Manage the activation of the engines
		switch (aEngineID) {
		case BMRB_Engine.ID:
			if (!flagActive && !this.dockWidget.widgets.toolButtonBMRB.checked) {
				this.dockWidget.widgets.toolButtonBMRB.checked = true;
				engine.active = this.dockWidget.widgets.toolButtonBMRB.checked;
				flagChange = false;
			} else {
				engine.active = this.dockWidget.widgets.toolButtonBMRB.checked;
			}
			break;
		case Emolecules_Engine.ID:
			if (!flagActive && !this.dockWidget.widgets.toolButtoneMolecules.checked) {
				this.dockWidget.widgets.toolButtoneMolecules.checked = true;
				engine.active = this.dockWidget.widgets.toolButtoneMolecules.checked;
				flagChange = false;
			} else {
				engine.active = this.dockWidget.widgets.toolButtoneMolecules.checked;
			}
			break;
		}

		//Update status bar messages
		if (flagChange) {
			if (engine.active) {
				this.dockWidget.widgets.labelStatusInfoData.text =  qsTr("{0} has been enabled successfully").format(engine.id);
			} else {
				this.dockWidget.widgets.labelStatusInfoData.text =  qsTr("{0} has been disabled successfully").format(engine.id);
			}
			settings.setValue(engine.enabledKey, engine.active);
		} else {
			this.dockWidget.widgets.labelStatusInfoData.text = "<font color=red>{0}</font>".format(qsTr("At least one search engine is needed. {0} has not been disabled.").format(engine.id));
		}

		this.guiAdaptionToFeatures();
	};

	MoleculeSearcherViewer.prototype.closeApplication = function () {

		this.molecule.flagTimerCompundsTable = this.secureSignalManagement(this.molecule.timerCompoundsTable.timeout, this.loadCompoundsTableSignal, "disconnect", this.molecule.flagTimerCompundsTable, this);
		this.dockWidget.close();
		Application.moleculeSearcher = undefined;
	};

	MoleculeSearcherViewer.prototype.parseMolFile = function (aTxtMolFile) {

		var result;

		//Remove certain endings which provokes bad molecule imports
		result = aTxtMolFile.toString().replace(/&#13;/, "");

		return result;
	};

	MoleculeSearcherViewer.prototype.resizeGUI = function (aItems, aShow, aHeight) {

		var currentItem, geometry;

		//Hide the not desired parts
		for (currentItem in aItems) {
			if (aItems.hasOwnProperty(currentItem)) {
				aItems[currentItem].visible = aShow;
			}
		}

		//Resize the height of the dock widget only it is not docked
		if (aHeight !== undefined && !isNaN(aHeight)) {
			if (this.dockWidget.floating) {

				//GUI adaption to the specific height
				geometry = {};
				geometry.width = this.dockWidget.width;
				geometry.height = aHeight;
				this.dockWidget.sizeHint = geometry;
				this.dockWidget.repaint();
			}
		}
	};

	MoleculeSearcherViewer.prototype.guiAdaptionToFeatures = function () {

		var i, j, engine, engines, feature, moleculeFeature, spectraFeature, structureSearchFeature, itemsToHide;

		moleculeFeature = false;
		spectraFeature = false;
		structureSearchFeature = false;
		itemsToHide = {};

		engines = this.availableEngines;
		for (i = 0; i < engines.length; i++) {
			engine = engines[i];
			if (engine.active) {
				for (j = 0; j < engine.features.length; j++) {
					feature = engine.features[j];
					switch (feature) {
					case MoleculeSearcher.moleculeFeatureID:
						moleculeFeature = true;
						break;
					case MoleculeSearcher.spectraFeatureID:
						spectraFeature = true;
						break;
					case MoleculeSearcher.structureSearchFeatureID:
						structureSearchFeature = true;
						break;
					default:
						break;
					}
				}
			}

			//Synchronize the status with the related button
			if ((engine !== undefined) && (engine.id !== undefined)) {
				if ((this.dockWidget.widgets["toolButton" + engine.id] !== undefined) && (this.dockWidget.widgets["toolButton" + engine.id].checked !== undefined)) {
					this.dockWidget.widgets["toolButton" + engine.id].checked = engine.active;
				}
			}
		}

		//Hide the features not needed
		itemsToHide = {};
		itemsToHide.groupBoxMoleculesResult = this.dockWidget.widgets.groupBoxMoleculesResult;
		itemsToHide.groupBoxMoleculesTabs = this.dockWidget.widgets.groupBoxMoleculesTabs;
		if (!moleculeFeature) {
			this.resizeGUI(itemsToHide, false);
		} else {
			this.resizeGUI(itemsToHide, true);
		}

		itemsToHide = {};
		itemsToHide.groupBoxSpectraResult = this.dockWidget.widgets.groupBoxSpectraResult;
		if (!spectraFeature) {
			this.resizeGUI(itemsToHide, false);
		} else {
			this.resizeGUI(itemsToHide, true);
		}

		//Reset tabs
		for (i = 0; i < 2; i++) {
			this.dockWidget.widgets.tabWidget.removeTab(0);
		}
		if (structureSearchFeature) {
			this.dockWidget.widgets.tabWidget.addTab(this.dockWidget.widgets.tabSearch, qsTr("Structure/Substructure Search"));
		}
		this.dockWidget.widgets.tabWidget.addTab(this.dockWidget.widgets.tabResult, qsTr("Structure Result"));
	};

	MoleculeSearcherViewer.prototype.secureSignalManagement = function (aSignal, aSlot, aAction, aFlag, aContext) {

		try {
			if (aAction === "connect" && !aFlag) {
				aSignal.connect(aContext, aSlot);
				aFlag = true;
			}
		} catch (e) {
			try {
				if (aAction === "connect" && !aFlag) {
					aSignal.connect(aContext, aSlot);
					aFlag = true;
				}
			} catch (er) {
				aFlag = false;
			}
		}

		try {
			if (aAction === "disconnect" && aFlag) {
				aSignal.disconnect(aContext, aSlot);
				aFlag = false;
			}
		} catch (err) {
			try {
				if (aAction === "disconnect" && aFlag) {
					aSignal.disconnect(aContext, aSlot);
					aFlag = false;
				}
			} catch (erro) {
				aFlag = true;
			}
		}

		return aFlag;
	};

	MoleculeSearcherViewer.prototype.loadSettingsOptions = function (aGUIObject) {

		var i, data, engine, featuresGroups, currentFeatureGroup, featuresGroup, currentFeature, guiType, auxGuiType, message;

		try {
			//General Options
			data =  settings.value(maximumHitsKey, "");
			if (data !== "") {
				aGUIObject.widgets.spinboxMaximumHits.value = data;
			}

			data =  settings.value(lastOptionTabIndex, "");
			if (data !== "") {
				aGUIObject.widgets.tabWidgetEngines.currentIndex = data;
			}

			//Load engine options settings
			for (i = 0; i < this.availableEngines.length; i++) {
				engine = this.availableEngines[i];
				featuresGroups = engine.engine.options;

				//Iterate through group of features of each engine (i.e. textSearch, substructure search, etc.)
				for (currentFeatureGroup in featuresGroups) {
					if (featuresGroups.hasOwnProperty(currentFeatureGroup)) {
						featuresGroup = engine.engine.options[currentFeatureGroup];

						//Iterate through a specific feature (i.e. hasSpectra, complexity, etc.)
						for (currentFeature in featuresGroup) {
							if (featuresGroup.hasOwnProperty(currentFeature)) {
								auxGuiType = currentFeature.split(engine.engine.optionsSettings.delimiter);
								guiType = auxGuiType[engine.engine.optionsSettings.guiTypeFieldNumber - 1];
								switch (guiType) {
								case MoleculeSearcher.comboboxGUITypeID:
									data = settings.value(engine.engine.options[currentFeatureGroup][currentFeature].registryKey, "-");
									if (data !== "-") {
										aGUIObject.widgets[currentFeature].currentText = data;
									}
									break;
								case MoleculeSearcher.checkboxGUITypeID:
									data = settings.value(engine.engine.options[currentFeatureGroup][currentFeature].registryKey, "");
									if (data !== "") {
										data = (data === "true");
										aGUIObject.widgets[currentFeature].checked = data;
									}
									break;
								case MoleculeSearcher.spinboxGUITypeID:
									data = settings.value(engine.engine.options[currentFeatureGroup][currentFeature].registryKey, "");
									if (data !== "") {
										aGUIObject.widgets[currentFeature].value = data;
									}
									break;
								default:
									MessageBox.critical(qsTr("Please, revise configuration of the script file regarding the {0} engine").format(engine.engine.id));
								}
							}
						}
					}
				}
			}
		} catch (e) {
			message = "<font color=red>{0}</font>".format(qsTr("An exception has occurred loading the options"));
		} finally {
			this.finalizeProgressBar(message);
		}
	};

	MoleculeSearcherViewer.prototype.saveSettingsOptions = function (aGUIObject, aLastIndexTab) {

		var i, engine, featuresGroups, currentFeatureGroup, featuresGroup, currentFeature, guiType, auxGuiType, message;

		try {
			//General Options
			settings.setValue(maximumHitsKey, aGUIObject.widgets.spinboxMaximumHits.value);
			settings.setValue(lastOptionTabIndex, aLastIndexTab);

			//Load engine options settings
			for (i = 0; i < this.availableEngines.length; i++) {
				engine = this.availableEngines[i];
				featuresGroups = engine.engine.options;

				//Iterate throw group of features of each engine (i.e. textSearch, substructure search, etc.)
				for (currentFeatureGroup in featuresGroups) {
					if (featuresGroups.hasOwnProperty(currentFeatureGroup)) {
						featuresGroup = engine.engine.options[currentFeatureGroup];

						//Iterate throw a specific feature (i.e. hasSpectra, complexity, etc.)
						for (currentFeature in featuresGroup) {
							if (featuresGroup.hasOwnProperty(currentFeature)) {
								auxGuiType = currentFeature.split(engine.engine.optionsSettings.delimiter);
								guiType = auxGuiType[engine.engine.optionsSettings.guiTypeFieldNumber - 1];
								switch (guiType) {
								case MoleculeSearcher.comboboxGUITypeID:
									settings.setValue(engine.engine.options[currentFeatureGroup][currentFeature].registryKey, aGUIObject.widgets[currentFeature].currentText);
									break;
								case MoleculeSearcher.checkboxGUITypeID:
									settings.setValue(engine.engine.options[currentFeatureGroup][currentFeature].registryKey, aGUIObject.widgets[currentFeature].checked);
									break;
								case MoleculeSearcher.spinboxGUITypeID:
									settings.setValue(engine.engine.options[currentFeatureGroup][currentFeature].registryKey, aGUIObject.widgets[currentFeature].value);
									break;
								default:
									MessageBox.critical(qsTr("Please, revise configuration of the script file regarding the {0} engine").format(engine.engine.id));
								}
							}
						}
					}
				}
			}
		} catch (e) {
			message = "<font color=red>{0}</font>".format(qsTr("An exception has occurred saving the options"));
		} finally {
			this.finalizeProgressBar(message);
		}
	};

	MoleculeSearcherViewer.prototype.warningMaxHits = function () {

		var messageWarning, buttonResponse;

		if ((this.options.lastMaximumHits > this.defaultMaximumHits) && (this.options.lastMaximumHits !== this.options.currentMaximumHits) && (this.options.lastMaximumHits !== -1)) {
			messageWarning = qsTr("You are requesting a range of <b>{0}</b> records. This is larger than the default maximium range of {1} records and may take long time. Do you want to continue anyway?").format(this.options.lastMaximumHits, this.defaultMaximumHits);
			buttonResponse = MessageBox.question(messageWarning);
			if (buttonResponse === MessageBox.No) {
				this.optionsDialog.show();
			} else {
				this.optionsDialog.accept();
			}
		} else {
			this.optionsDialog.accept();
		}
	};

	MoleculeSearcherViewer.prototype.processComboboxTextValues = function (aCombobox, aInitialToken, aEndToken, aRestoreValues) {

		var i, currentItem, originalData, newData, auxItem, currentIndex, initpos, endpos;

		//Get the object and the real parameter name using the UI name
		if (aCombobox !== undefined && aCombobox.currentIndex !== undefined) {
			currentIndex = aCombobox.currentIndex;
			if (aRestoreValues === undefined) {
				originalData = [];
				newData = [];
				for (i = 0; i < aCombobox.count; i++) {

					//Get current value
					aCombobox.currentIndex = i;
					currentItem = aCombobox.currentText;

					//Get original data
					originalData.push(currentItem);

					//Get user data
					auxItem = currentItem;
					initpos = currentItem.lastIndexOf(aInitialToken);
					if (aEndToken !== undefined) {
						endpos = currentItem.lastIndexOf(aEndToken);
						if (initpos !== -1 && endpos !== -1) {
							auxItem = currentItem.substring((initpos + 1), endpos);
						} else if (initpos !== -1) {
							auxItem = currentItem.substring((initpos + 1), currentItem.length);
						}
					} else if (initpos !== -1) {
						auxItem = currentItem.substring((initpos + 1), currentItem.length);
					}
					newData.push(auxItem);
				}
			} else {
				newData = aRestoreValues;
			}

			//We built again the combobox with generic items, only writing the relevant information
			aCombobox.clear();
			for (i = 0; i < newData.length; i++) {
				aCombobox.addItem(newData[i]);
			}

			//Retore the original currentIndex
			aCombobox.currentIndex = currentIndex;

			return originalData;
		}
	};

	MoleculeSearcherViewer.prototype.optionsManagement = function () {

		var lastIndexTab, dialogResult;

		//We abort if there is an active process still running
		if (this.activeProcess()) {
			return;
		}

		//Load registry options and show dialog
		this.loadSettingsOptions(this.optionsDialog);
		this.optionsDialog.show();

		lastIndexTab = this.optionsDialog.widgets.tabWidgetEngines.currentIndex;
		this.options.currentMaximumHits = this.optionsDialog.widgets.spinboxMaximumHits.value;
		this.options.lastMaximumHits = -1;

		//Signal to handle the change of the current engine tab, maximum hits value and dialog button
		this.optionsDialog.widgets.tabWidgetEngines.currentChanged.connect(this, function (aIndex) { lastIndexTab = aIndex; });
		this.optionsDialog.widgets.spinboxMaximumHits['valueChanged(int)'].connect(this, function (aValue) {this.options.lastMaximumHits = aValue; });
		this.options.flagButtonBox = this.secureSignalManagement(this.optionsDialog.widgets.buttonBox.accepted, this.warningMaxHits, "connect", this.options.flagButtonBox, this);

		//Receive the kind of button clicked by user once the dialog has been closed and siconnect the signal
		dialogResult = this.optionsDialog.exec();
		this.options.flagButtonBox = this.secureSignalManagement(this.optionsDialog.widgets.buttonBox.accepted, this.warningMaxHits, "disconnect", this.options.flagButtonBox, this);

		//At this point we save the options if it is needed
		if (!dialogResult) {
			//If not, we restore the inital values in order to discard all possible changes because the user clicked the cancel button of the dialog
			this.loadSettingsOptions(this.optionsDialog);
			this.dockWidget.widgets.labelStatusInfoData.text = "<font color='red'>{0}</font>".format(qsTr("The options have not been saved"));
		} else {
			this.saveSettingsOptions(this.optionsDialog, lastIndexTab);
			this.dockWidget.widgets.labelStatusInfoData.text = qsTr("The options have been saved sucessfully");
		}
	};

	MoleculeSearcherViewer.prototype.initializeProgressBar = function () {

		this.dockWidget.widgets.progressBar.visible = true;
		this.dockWidget.widgets.toolButtonCancel.visible = true;
		this.dockWidget.widgets.toolButtonExit.visible = false;
		this.dockWidget.widgets.progressBar.wasCanceled = false;
		this.dockWidget.widgets.progressBar.value = 0;
	};

	MoleculeSearcherViewer.prototype.finalizeProgressBar = function (aStatusMessage) {

		this.dockWidget.widgets.progressBar.value = 100;
		this.dockWidget.widgets.progressBar.visible = false;
		this.dockWidget.widgets.progressBar.wasCanceled = false;
		this.dockWidget.widgets.toolButtonCancel.visible = false;
		this.dockWidget.widgets.toolButtonExit.visible = true;
		if (aStatusMessage !== undefined) {
			this.dockWidget.widgets.labelStatusInfoData.text = aStatusMessage;
		}
	};

	MoleculeSearcherViewer.prototype.checkEngines = function () {

		var i, flagActive, engine;

		//It manages if a single engine is installed
		if (this.availableEngines.length === 0) {
			MessageBox.critical("At least a database is needed to run this application");
			return;
		}

		//Fix problems related to old removed engines
		if (this.availableEngines.length > 0) {
			flagActive = false;
			for (i = 0; i < this.availableEngines.length; i++) {
				engine = this.availableEngines[i];
				if (engine.active !== undefined && engine.active) {
					flagActive = true;
					break;
				}
			}

			//Enable all engines if there is not one active at least
			if (!flagActive) {
				for (i = 0; i < this.availableEngines.length; i++) {
					engine = this.availableEngines[i];
					engine.active = true;
				}
			}
		}
	};

	MoleculeSearcherViewer.prototype.cleanWidget = function (aStatusMessage) {

		//Disconnect signals
		this.molecule.flagCurrentCellChanged = this.secureSignalManagement(this.molecule.tableWidget.currentCellChanged, this.moleculeChanged, "disconnect", this.molecule.flagCurrentCellChanged, this);

		//Clear structure data
		this.molecule.result = ManageStructureData.createStructureFromTableWidgetFields(this.molecule.result, this.molecule.fields, this.molecule.extraFields);
		this.molecule.totalRowsChecked = 0;
		this.molecule.rowClicked = -1;
		this.spectra.result = ManageStructureData.createStructureFromTableWidgetFields(this.spectra.result, this.spectra.fields, this.spectra.extraFields);
		this.spectra.totalRowsChecked = 0;
		this.spectra.rowClicked = -1;

		//Clear search form molecule
		this.molecule.tbTextSearch.enabled = false;
		this.molecule.tbStructureSearch.enabled = false;
		this.molecule.tbSubstructureSearch.enabled = false;
		this.dockWidget.setFocus();
		this.molecule.leTextInput.setFocus();
		if (this.molecule.leTextInput.text !== "") {
			this.molecule.tbTextSearch.enabled = true;
		}

		//Clear structure search and structure result tabs
		this.dockWidget.widgets.lbActiveMoleculeSearch.text = qsTr("Select a molecule on page or table of compounds,\n click the blue arrow button and press the search button");
		this.dockWidget.widgets.lbActiveMoleculeResult.text = qsTr("Make a search to begin the process");
		this.molecule.tbActiveResultImport.enabled = false;
		this.molecule.tbActiveLoad.enabled = false;

		//Clear molecules result
		this.clearTable(this.molecule.tableWidget, this.dockWidget.widgets.progressBar);
		this.molecule.tbCheckAll.enabled = false;
		this.molecule.tbUncheckAll.enabled = false;
		this.molecule.tbImport.enabled = false;
		this.molecule.tbDownload.enabled = false;
		this.molecule.tableWidget.resizeColumnToContents(0);
		this.molecule.tableWidget.enabled = false;
		this.molecule.rowClicked = -1;
		this.molecule.lastRowClicked = -1;

		//Clear spectra panel
		this.clearTable(this.spectra.tableWidget, this.dockWidget.widgets.progressBar);
		this.spectra.tableWidget.clearContents();
		this.spectra.tableWidget.enabled = false;
		this.spectra.tbCheckAll.enabled = false;
		this.spectra.tbUncheckAll.enabled = false;
		this.spectra.tbImport.enabled = false;
		this.spectra.tbDownload.enabled = false;
		this.finalizeProgressBar();

		//Clear status bar
		this.dockWidget.widgets.labelStatusInfoData.text = "";
		if (aStatusMessage !== undefined && aStatusMessage !== "") {
			this.dockWidget.widgets.labelStatusInfoData.text = aStatusMessage;
		}

		//Connect signals
		this.molecule.flagCurrentCellChanged = this.secureSignalManagement(this.molecule.tableWidget.currentCellChanged, this.moleculeChanged, "connect", this.molecule.flagCurrentCellChanged, this);
	};

	MoleculeSearcherViewer.prototype.activeProcess = function () {

		var result;

		result = false;
		if (this.molecule.flagSearch) {
			this.activeProcessManagement(this.molecule, qsTr("An active molecule search is still in progress"));
			result = true;
		} else if (this.molecule.flagImport) {
			this.activeProcessManagement(this.molecule, qsTr("An active molecule import process is still in progress"));
			result = true;
		} else if (this.molecule.flagDownload) {
			this.activeProcessManagement(this.molecule, qsTr("An active molecule download process is still in progress"));
			result = true;
		} else if (this.molecule.flagImage) {
			this.activeProcessManagement(this.molecule, qsTr("The image of the molecule is still being downloaded"));
			result = true;
		} else if (this.spectra.flagImport && this.dockWidget.widgets.groupBoxSpectraResult.visible) {
			this.activeProcessManagement(this.molecule, qsTr("An active spectra import process is still in progress"));
			result = true;
		} else if (this.spectra.flagDownload && this.dockWidget.widgets.groupBoxSpectraResult.visible) {
			this.activeProcessManagement(this.molecule, qsTr("An active spectra download process is still in progress"));
			result = true;
		}

		return result;
	};

	MoleculeSearcherViewer.prototype.thereIsAnActiveProcess = function () {

		var result;

		result = (this.molecule.flagSearch || this.molecule.flagImport || this.molecule.flagDownload || this.molecule.flagImage || this.spectra.flagImport || this.spectra.flagDownload || this.spectra.flagSearch);

		return result;
	};

	MoleculeSearcherViewer.prototype.activeProcessManagement = function (aObject, aMessage) {

		//It performs the undo in the table
		if (aObject.lastRowClicked > -1) {
			//Disconnect, change current row and connect signals to avoid a endless loop causing a stack-overflow issue
			this.molecule.flagCurrentCellChanged = this.secureSignalManagement(this.molecule.tableWidget.currentCellChanged, this.moleculeChanged, "disconnect", this.molecule.flagCurrentCellChanged, this);
			aObject.tableWidget.selectRow(aObject.lastRowClicked);
			//Connect the signal again because the selection has already been changed in the previous statement
			this.molecule.flagCurrentCellChanged = this.secureSignalManagement(this.molecule.tableWidget.currentCellChanged, this.moleculeChanged, "connect", this.molecule.flagCurrentCellChanged, this);
			aObject.tableWidget.update();
		}

		//Given that we have carried out an undo regarding the selection row, the current row is the last row
		aObject.rowClicked = aObject.lastRowClicked;

		MessageBox.warning(aMessage);
	};

	MoleculeSearcherViewer.prototype.getImageFittedToLabel = function (aFilePathImage, aLabel, aPixmap, aFitImage) {

		var pixmap, resize, resizeWidth, resizeHeight;

		pixmap = new Pixmap();
		pixmap.load(aFilePathImage);
		resize = 1;

		//Get the specified pixmap if exists
		if (aPixmap !== undefined) {
			pixmap = aPixmap;
		}

		//Get constant for each dimension
		if (aFitImage !== undefined && aFitImage) {
			if (pixmap.width > aLabel.width) {
				resizeWidth = Number(aLabel.width) / Number(pixmap.width);
				resize = resizeWidth;
			}
			if (pixmap.height > aLabel.height) {
				resizeHeight = Number(aLabel.height) / Number(pixmap.height);
				resize = resizeHeight;
			}

			//Get the lower real number constant to scale both dimensions
			if (resizeHeight !== undefined && !isNaN(resizeHeight) && (resizeHeight < resizeWidth)) {
				resize = resizeHeight;
			} else if (resizeWidth !== undefined && !isNaN(resizeWidth) && (resizeHeight >= resizeWidth)) {
				resize = resizeWidth;
			}
		}

		//Decide if we need to scale image or it doesn't make sense.
		if (resize !== undefined && resize !== 1 && aFitImage !== undefined && aFitImage) {
			aLabel.pixmap = pixmap.scaled(pixmap.width * resize, pixmap.height * resize, 1, 1);
		} else {
			aLabel.pixmap = pixmap;
		}
	};

	MoleculeSearcherViewer.prototype.getMoleculeImage = function (aURL, aPixmap, aEngine) {

		var tempDir, resultFile, engineResult, allResponse, allErrorOutput, engineID, message;

		tempDir = new Dir(Dir.temp());
		this.tempImage = new File(tempDir.filePath("temp.png"));
		engineID = "search database";

		if (aPixmap !== undefined) {
			this.getImageFittedToLabel(undefined, this.dockWidget.widgets.lbActiveMoleculeSearch, aPixmap, this.fitMoleculeImage);
		} else {
			if (aURL === undefined) {
				serialization.save(this.tempImage.name, "png");
				this.getImageFittedToLabel(this.tempImage.name, this.dockWidget.widgets.lbActiveMoleculeSearch, undefined, this.fitMoleculeImage);
			} else {

				if (aEngine !== undefined) {
					engineID = aEngine;
				}

				//Show connection message to the users and get data from response object.
				this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Connecting to {0}...").format(engineID);
				engineResult = this.webUtils.request(aURL, "", "", "", true);
				allResponse = engineResult.response;
				allErrorOutput = engineResult.allErrorOutput;

				//Control errors
				if (!MoleculeSearcher.controlErrors(allResponse, allErrorOutput, engineResult.exitCode, engineID, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData, true)) {
					this.tempImage.open(File.ReadWrite);
					resultFile = new BinaryStream(this.tempImage);
					resultFile.writeBytes(engineResult.response);
					this.tempImage.close();
					this.getImageFittedToLabel(this.tempImage.name, this.dockWidget.widgets.lbActiveMoleculeResult, undefined, this.fitMoleculeImage);
					this.dockWidget.widgets.labelStatusInfoData.text = '<font color="darkgreen">{0}</font>'.format(qsTr("Molecule image loaded successfully from {0}").format(engineID));

				} else {
					this.dockWidget.widgets.lbActiveMoleculeResult.text = qsTr("Unable to get\nthe molecule image\nfrom {0}".format(engineID));
					message = "<font color=red>{0}</font>".format(qsTr("Unable to get the molecule image from {0}").format(engineID));
					this.finalizeProgressBar(message);
				}
			}
		}
	};

	MoleculeSearcherViewer.prototype.loadActiveMolecule = function () {

		var compoundTableState;

		//We abort if there is an active process still running
		if (this.activeProcess()) {
			return;
		}

		//Keep the compound table selection. Available only since revision 15955 relate to 10.1.0 (issue 9331)
		this.cleanWidget();
		compoundTableState = mainWindow.activeDocument.selectedMolTableItems();
		if (compoundTableState.length > 0) {
			this.deselectedByClickingLoadActiveMolecule = true;
		}

		//Clear the compound table selection
		mainWindow.activeDocument.setSelectedMolTableItems([]);

		//Get the image related to the current molecule selected by the user. By clearing the compound table selection we avoid the problem related to issue 9280
		if (this.molecule.compoundTablePixmap !== undefined) {
			this.getMoleculeImage(undefined, this.molecule.compoundTablePixmap);
			this.molecule.compoundTablePixmap = undefined;
		} else {
			this.getMoleculeImage();
		}

		//Now we restore the compound table selection
		mainWindow.activeDocument.setSelectedMolTableItems(compoundTableState);
		this.lastMolFileStructureSearchLoaded = this.lastMolFileStructureSearch;
		this.lastAtomsNumberStructureSearchLoaded = this.lastAtomsNumberStructureSearch;
		this.lastBondNumberStructureSearchLoaded = this.lastBondNumberStructureSearch;
		this.molecule.tbStructureSearch.enabled = true;
		this.molecule.tbSubstructureSearch.enabled = true;
		this.molecule.tbActiveLoad.enabled = false;
		this.dockWidget.widgets.labelStatusInfoData.text = '<font color="darkgreen">{0}</font>'.format(qsTr("Molecule loaded successfully. Now click either structure search or substructure search button."));
	};

	MoleculeSearcherViewer.prototype.applyRequiredActions = function (aStringValue, aTypeAction, aDelimiter, aActionFieldNumber, aDataFieldNumber) {

		var result, aux, action, token;

		result = aStringValue;
		if (aTypeAction !== undefined) {

			//Get fields
			aux = aTypeAction.split(aDelimiter);
			action = aux[aActionFieldNumber - 1];
			token = aux[aDataFieldNumber - 1];

			switch (action) {
			case "remove":
				result = aStringValue.toString().replace(new RegExp(token, 'g'), "");
				break;
			case "getBefore":
				result = aStringValue.toString().split(token)[aActionFieldNumber - 1];
				break;
			}
		}

		return result;
	};

	MoleculeSearcherViewer.prototype.getGUIOptions = function (aObject, aParameters, aDelimiter, aGUITypeFieldNumber, aParameterNameFieldNumber, aRequiredActionDelimiter, aRequiredActionTypeFieldNumber, aRequiredActionDataFieldNumber) {

		var currentItem, optionsParameter, aux, componentType, parameterName, parameterValue;

		//Check if the parameters are fine
		if (aObject !== undefined && Object.keys(aObject).length > 0) {
			if (aParameters === undefined) {
				optionsParameter = {};
			} else {
				optionsParameter = aParameters;
			}

			//Retrieve all options from GUI object
			for (currentItem in aObject) {
				if (aObject.hasOwnProperty(currentItem)) {

					//The component type comes before the underscore symbol
					aux = currentItem.split(aDelimiter);
					componentType = aux[aGUITypeFieldNumber - 1];
					parameterName = aux[aParameterNameFieldNumber - 1];

					//Get data depending on the GUI type component
					switch (componentType) {
					case MoleculeSearcher.checkboxGUITypeID:
						parameterValue = aObject[currentItem].checked;
						break;
					case MoleculeSearcher.comboboxGUITypeID:
						parameterValue = aObject[currentItem].currentIndex;
						break;
					case MoleculeSearcher.spinboxGUITypeID:
						parameterValue = aObject[currentItem].value;
						break;
					}
					optionsParameter[parameterName] = this.applyRequiredActions(parameterValue, aObject[currentItem].requiredAction, aRequiredActionDelimiter, aRequiredActionTypeFieldNumber, aRequiredActionDataFieldNumber);
				}
			}
		}

		return optionsParameter;
	};

	MoleculeSearcherViewer.prototype.updateCheckedRows = function (aObject, aRow) {

		var i, items, counter, fieldNumber;

		aObject.tbCheckAll.enabled = true;
		aObject.tbUncheckAll.enabled = true;
		aObject.tbImport.enabled = false;
		aObject.tbDownload.enabled = false;
		fieldNumber = aObject.statusFieldNumber;

		counter = aObject.totalRowsChecked;
		items = aObject.tableWidget.items;
		if (aRow !== undefined) {
			if (items[aRow][fieldNumber].checkState === 2) {
				counter++;
			} else if ((items[aRow][fieldNumber].checkState === 0)) {
				counter--;
			}
		} else {
			counter = 0;
			for (i = 0; i < items.length; i++) {
				if (items[i][fieldNumber].checkState === 2) {
					counter++;
				}
			}
		}

		if (counter > 0) {
			aObject.tbImport.enabled = true;
			aObject.tbDownload.enabled = true;
		}
		if (counter === items.length) {
			aObject.tbCheckAll.enabled = false;
		}
		if (counter === 0) {
			aObject.tbUncheckAll.enabled = false;
		}

		aObject.statusBar.text = '<font color="black">{0}</font>'.format(qsTr("{0} checked {1}").format(aObject.ID, counter));
		aObject.totalRowsChecked = counter;

		return counter;
	};

	MoleculeSearcherViewer.prototype.cellChanged = function (row, col) {

		if (col === this.statusFieldNumber) {
			this.totalRowsChecked = this.updateRows(this, row);
		}
	};

	MoleculeSearcherViewer.prototype.clearTable = function (aTableWidget, aProgressBar) {

		var i, auxLength;

		if (aTableWidget !== undefined) {
			auxLength = aTableWidget.items.length;
			aTableWidget.clearContents();
			for (i = 0; i < auxLength && !aProgressBar.wasCanceled; i++) {
				aTableWidget.removeRow(0);
			}
		}
	};

	MoleculeSearcherViewer.prototype.fillTable = function (aObject, aEngine, aFeature, aTotalNewEntries, aProgressBar, aSpectraArray, aMaxRows) {

		var i, j, items, value, fields, field, badRecords, maxRows, totalNewEntries;

		badRecords = 0;
		if (aEngine.engine.badRecordsCounter !== undefined && !isNaN(aEngine.engine.badRecordsCounter)) {
			badRecords = aEngine.engine.badRecordsCounter;
		}

		totalNewEntries = aTotalNewEntries;
		maxRows = aMaxRows - badRecords;

		aObject.tableWidget.enabled = true;
		if (aFeature === MoleculeSearcher.spectraFeatureID) {
			this.clearTable(aObject.tableWidget, aProgressBar);
		}

		//Insert empty rows
		for (i = 0; (i < totalNewEntries) && (i < maxRows) && (!aProgressBar.wasCanceled); i++) {
			aObject.tableWidget.insertRow(0);
		}

		//Add items and fill the table with the values
		items = aObject.tableWidget.items;
		fields = aObject.fields;

		//Fill the molecule table's with the search result
		for (i = 0; (i < totalNewEntries) && (i < maxRows) && (!aProgressBar.wasCanceled); i++) {
			for (j = 0; j < fields.length; j++) {
				field = fields[j];
				value = aObject.result[field][i];
				if (field === "") {
					items[i][j].text = "";
					items[i][j].flags = 49;
					items[i][j].checkState = 0;
				} else {
					items[i][j].text = value;
					items[i][j].flags = 33;
				}
			}
		}
		aObject.tableWidget.items = items;

		//Auto select first row, show image and manage progress bar and buttons and show tab result
		if (aFeature === MoleculeSearcher.moleculeFeatureID) {
			//This control avoids problem to current image of other engine if we could get at least a entry
			if (totalNewEntries > 0) {
				this.dockWidget.widgets.tabWidget.setCurrentIndex(1);
				if (aEngine.engine.ID === Emolecules_Engine.ID) {
					this.getMoleculeImage(aEngine.engine.molecule.URLImage.format(aObject.result.ENC_ID[0]), undefined, aEngine.engine.ID);
				} else {
					this.getMoleculeImage(aEngine.getUrlImageFromId(aObject.result.ID[0]), undefined, aEngine.engine.ID);
				}
			}
			this.molecule.tbActiveResultImport.enabled = true;
		} else if (aFeature === MoleculeSearcher.spectraFeatureID && aSpectraArray !== undefined) {
			aSpectraArray.push(aObject.result);
		}

		aObject.tableWidget.resizeColumnToContents(0);
		aObject.tbCheckAll.enabled = true;
		aObject.tbUncheckAll.enabled = false;
		aObject.tbImport.enabled = false;
		aObject.tbDownload.enabled = false;
	};

	MoleculeSearcherViewer.prototype.moleculeChanged = function () {

		//Perform search
		this.molecule.flagCellDoubleClicked = this.secureSignalManagement(this.molecule.tableWidget.cellDoubleClicked, this.cellDoubleClicked, "disconnect", this.molecule.flagCellDoubleClicked, this);
		this.spectraSearch(this.molecule.tableWidget.currentRow);
		this.molecule.flagCellDoubleClicked = this.secureSignalManagement(this.molecule.tableWidget.cellDoubleClicked, this.cellDoubleClicked, "connect", this.molecule.flagCellDoubleClicked, this);

	};

	MoleculeSearcherViewer.prototype.cellDoubleClicked = function (row, col) {

		this.molecule.flagCellDoubleClicked = this.secureSignalManagement(this.molecule.tableWidget.cellDoubleClicked, this.cellDoubleClicked, "disconnect", this.molecule.flagCellDoubleClicked, this);
		this.spectraSearch(row, col);
		this.molecule.flagCellDoubleClicked = this.secureSignalManagement(this.molecule.tableWidget.cellDoubleClicked, this.cellDoubleClicked, "connect", this.molecule.flagCellDoubleClicked, this);
	};

	MoleculeSearcherViewer.prototype.spectraSearch = function (row) {

		var engine, engineResult, allResponse, allErrorOutput, structureDataManagement, message, flagError, aux;

		//Get the last selected row if there is not an active process
		if (!(this.thereIsAnActiveProcess())) {
			this.molecule.lastRowClicked = this.molecule.rowClicked;
			this.molecule.rowClicked = row;
		} else {
			//Swap values if an active process is still running to send the right lastRowClicked value to  the activeProcessManagement method
			aux = this.molecule.rowClicked;
			this.molecule.rowClicked = this.molecule.lastRowClicked;
			this.molecule.lastRowClicked = aux;
		}

		//We abort if there is an active process still running
		if (this.activeProcess()) {
			return;
		}

		//We make the actions only if user pushes out of checkbox column.
		//We do not update the spectra data if the molecules search has not finished yet
		if (!this.spectra.semaphoreSearch  && !this.molecule.flagSearch) {
			if (this.spectra.tableWidget.items.length < 1 || this.molecule.lastRowClicked !== row) {
				if (this.spectra.flagDownload) {
					this.activeProcessManagement(this.molecule, qsTr("An active spectra download process is still in progress"));
				} else if (this.spectra.flagImport) {
					this.activeProcessManagement(this.molecule, qsTr("An active spectra import process is still in progress"));
				} else {

					//Get the right engine
					try {
						engine = MoleculeSearcher.getEngineInstance(this.molecule.result.Database[row]);
						message = undefined;

						//We perform the search only if the engine is active and is able to do it
						if (engine.active && MoleculeSearcher.hasFeature(MoleculeSearcher.spectraFeatureID, engine)) {

							//Active the flag only if the engine has the spectra feature
							this.spectra.flagSearch = true;

							//Active the semaphore and instance a class to manage the XML data
							structureDataManagement = new ManageStructureData();
							this.spectra.semaphoreSearch = true;

							//Initialize progress bar and structure data
							this.spectra.statusBar.text = qsTr("Connecting to {0}...").format(engine.engine.ID);
							this.initializeProgressBar();
							this.spectra.result = ManageStructureData.createStructureFromTableWidgetFields(this.spectra.result, this.spectra.fields, this.spectra.extraFields);

							//Clear spectra panel and set the image in result tab
							this.spectra.tableWidget.enabled = false;
							this.spectra.tbCheckAll.enabled = false;
							this.clearTable(this.spectra.tableWidget, this.dockWidget.widgets.progressBar);
							this.molecule.flagImage = true;

							//It manages the encrypted ID for eMolecules engine
							if (engine.engine.ID === Emolecules_Engine.ID) {
								this.getMoleculeImage(engine.engine.molecule.URLImage.format(this.molecule.result.ENC_ID[row]), undefined, engine.engine.ID);
							} else {
								this.getMoleculeImage(engine.getUrlImageFromId(this.molecule.result.ID[row]), undefined, engine.engine.ID);
							}

							//Reset flag image for molecule
							this.molecule.flagImage = false;

							//Get Spectra info from Webservice engine
							this.spectra.statusBar.text = qsTr("Retrieving associated spectra for selected molecule from {0}").format(engine.engine.ID);
							engineResult = engine.getCompoundSpectraInfo(this.molecule.result.ID[row]);
							allResponse = engineResult.response;
							allErrorOutput = engineResult.allErrorOutput;

							//Control errors
							if (MoleculeSearcher.controlErrors(allResponse, allErrorOutput, engineResult.exitCode, engine.engine.ID, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData)) {
								this.spectra.semaphoreSearch = false;
								flagError = true;

							} else {

								//Control error if we don't receive any spectrum data
								if (allResponse.length === 0) {
									this.clearTable(this.spectra.tableWidget, this.dockWidget.widgets.progressBar);
									this.spectra.tableWidget.enabled = false;
									this.spectra.tbCheckAll.enabled = false;
									message = '<font color = "red">{0}</font>'.format(qsTr("There is no spectrum data associated with this molecule"));

								} else {
									//Set information from the response
									engine[engine.engine.spectra.structureSearchMethod](this.dockWidget.widgets.progressBar, this.spectra.result, allResponse, this.molecule.result.ID[row]);

									//Insert table rows before fill the table with the data
									this.spectra.flagCellChanged = this.secureSignalManagement(this.spectra.tableWidget.cellChanged, this.cellChanged, "disconnect", this.spectra.flagCellChanged, this.spectra);

									//Fill the table with the spectral data
									this.fillTable(this.spectra, engine, MoleculeSearcher.spectraFeatureID, this.spectra.result.ID.length, this.dockWidget.widgets.progressBar, this.molecule.result.Spectra, this.options.spinboxMaximumHits.value);

									//Set message, connect signal and update checked rows
									this.spectra.flagCellChanged = this.secureSignalManagement(this.spectra.tableWidget.cellChanged, this.cellChanged, "connect", this.spectra.flagCellChanged, this.spectra);
									this.spectra.totalRowsChecked = this.updateCheckedRows(this.spectra);
									message = '<font color = "darkgreen">{0}</font>'.format(qsTr("Spectra found {0}").format(this.spectra.result.ID.length));
								}
							}
						} else {

							//If the engine does not have the spectra feature we use a special flag to ensure we get the image of the molecule
							//Set the image in result tab and show error about not availability
							this.molecule.flagImage = true;

							//It manages the encrypted ID for eMolecules engine
							if (engine.id === Emolecules_Engine.ID) {
								this.getMoleculeImage(engine.engine.molecule.URLImage.format(this.molecule.result.ENC_ID[row]), undefined, engine.id);
							} else {
								this.getMoleculeImage(engine.getUrlImageFromId(this.molecule.result.ID[row]), undefined, engine.id);
							}

							//Clear both table and flag image for molecule. It also update the status bar 
							this.molecule.flagImage = false;
							this.clearTable(this.spectra.tableWidget, this.dockWidget.widgets.progressBar);
							this.spectra.tableWidget.enabled = false;
							if (this.dockWidget.widgets.groupBoxSpectraResult.visible) {
								message = '<font color = "red">{0}</font>'.format(qsTr("Spectra feature not available for the {0} engine").format(this.molecule.result.Database[row]));
							} else {
								message = undefined;
							}
						}

					} catch (e) {
						this.clearTable(this.spectra.tableWidget, this.dockWidget.widgets.progressBar);
						message = "<font color=red>{0}</font>".format(qsTr("An exception has occurred during the spectra search"));
					} finally {
						this.finalizeProgressBar(message);
						this.molecule.flagImage = false;
						this.spectra.semaphoreSearch = false;
						this.spectra.flagSearch = false;

					}
				}
			}
		}
	};

	MoleculeSearcherViewer.prototype.moleculeTextSearch = function (aEngine) {

		var engine, engineResult, allResponse, allErrorOutput, compoundRetrieved, structureDataManagement, value, parameters, newEntries, oldEntries;

		//To perform the method both the engine and the feature condition must be met
		if (aEngine.active) {

			if (MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, aEngine)) {
				structureDataManagement = new ManageStructureData();
				engine = aEngine;
				value = this.molecule.leTextInput.text;

				//Initialize progress bar and get the engine parameters
				this.initializeProgressBar();
				parameters = this.getGUIOptions(engine.engine.options.textSearch, undefined, engine.engine.optionsSettings.delimiter, engine.engine.optionsSettings.guiTypeFieldNumber, engine.engine.optionsSettings.parameterNameFieldNumber, engine.engine.optionsSettings.requiredActionDelimiter, engine.engine.optionsSettings.requiredActionTypeFieldNumber, engine.engine.optionsSettings.requiredActionDataFieldNumber);

				//Update status info to the user
				this.dockWidget.widgets.progressBar.value = this.dockWidget.widgets.progressBar.value + 5;
				this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Connecting to {0}...").format(engine.engine.ID);

				//Get molecule data from webservice
				engineResult = engine[engine.engine.molecule.textSearchMethod](value, parameters, this.options.spinboxMaximumHits.value, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData);
				allResponse = engineResult.response;
				allErrorOutput = engineResult.allErrorOutput;

				//Control errors
				if (MoleculeSearcher.controlErrors(allResponse, allErrorOutput, engineResult.exitCode, engine.engine.ID, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData)) {
					return false;
				}

				//Get array of IDs from search data
				compoundRetrieved = structureDataManagement[engine.engine.molecule.structureDataManagementMethod](allResponse, engine.engine.molecule.iDArrayElement);
				this.dockWidget.widgets.progressBar.value = this.dockWidget.widgets.progressBar.value + 5;

				//Getting the data of the molecules received above
				oldEntries = this.molecule.result.hasOwnProperty("Database") ? this.molecule.result.Database.length : 0;
				this.molecule.result = engine[engine.engine.molecule.detailMethod](compoundRetrieved, this.dockWidget.widgets.progressBar, this.molecule.result, allResponse, this.options.spinboxMaximumHits.value, this.dockWidget.widgets.labelStatusInfoData);
				newEntries = this.molecule.result.Database.length - oldEntries;

				//Fill the molecules data grid
				this.fillTable(this.molecule, engine, MoleculeSearcher.moleculeFeatureID, newEntries, this.dockWidget.widgets.progressBar, undefined, this.options.spinboxMaximumHits.value);
			}
		}

		return true;
	};

	MoleculeSearcherViewer.prototype.moleculeStructureSearch = function (aEngine) {

		var engine, engineResult, allResponse, allErrorOutput, compoundRetrieved, xmlManagement, value, result, parameters;

		result = false;

		//To perform the method both the engine and the feature condition must be met
		if (aEngine.active) {

			if (MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, aEngine)) {

				if (this.lastMolFileStructureSearchLoaded !== undefined && this.lastMolFileStructureSearchLoaded !== "") {
					value = this.lastMolFileStructureSearchLoaded;
					xmlManagement = new ManageStructureData();
					engine = aEngine;

					//Initialize progress bar and get the engine parameters
					this.initializeProgressBar();
					parameters = this.getGUIOptions(engine.engine.options.structureSearch, undefined, engine.engine.optionsSettings.delimiter, engine.engine.optionsSettings.guiTypeFieldNumber, engine.engine.optionsSettings.parameterNameFieldNumber, engine.engine.optionsSettings.requiredActionDelimiter, engine.engine.optionsSettings.requiredActionTypeFieldNumber, engine.engine.optionsSettings.requiredActionDataFieldNumber);

					//Update status info to the user
					this.dockWidget.widgets.progressBar.value = this.dockWidget.widgets.progressBar.value + 5;
					this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Connecting to {0}...").format(engine.engine.ID);

					//Get molecule data from webservice
					engineResult = engine[engine.engine.molecule.structureSearchMethod](value, parameters, this.options.spinboxMaximumHits.value, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData);
					allResponse = engineResult.response;
					allErrorOutput = engineResult.allErrorOutput;

					//Control errors
					if (MoleculeSearcher.controlErrors(allResponse, allErrorOutput, engineResult.exitCode, engine.engine.ID, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData)) {
						return false;
					}

					//Get array of IDs from data
					compoundRetrieved = xmlManagement.getElementsFromXML(allResponse, engine.engine.molecule.iDArrayElement);
					this.dockWidget.widgets.progressBar.value = this.dockWidget.widgets.progressBar.value + 5;

					//Getting the data of the molecules received above
					this.molecule.result = engine[engine.engine.molecule.detailMethod](compoundRetrieved, this.dockWidget.widgets.progressBar, this.molecule.result, allResponse, this.options.spinboxMaximumHits.value, this.dockWidget.widgets.labelStatusInfoData);

					//Fill the molecules data grid
					this.fillTable(this.molecule, engine, MoleculeSearcher.moleculeFeatureID, compoundRetrieved.length, this.dockWidget.widgets.progressBar, undefined, this.options.spinboxMaximumHits.value);
					result = true;
				}
			}
		}

		return result;
	};

	MoleculeSearcherViewer.prototype.moleculeSubstructureSearch = function (aEngine) {

		var engine, engineResult, allResponse, allErrorOutput, compoundRetrieved, xmlManagement, value, result, parameters;

		result = false;

		//To perform the method both the engine and the feature condition must be met
		if (aEngine.active) {

			if (MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, aEngine)) {

				if (this.lastMolFileStructureSearchLoaded !== undefined && this.lastMolFileStructureSearchLoaded !== "") {
					value = this.lastMolFileStructureSearchLoaded;
					xmlManagement = new ManageStructureData();
					engine = aEngine;

					//Initialize progress bar and get the engine parameters
					this.initializeProgressBar();
					parameters = this.getGUIOptions(engine.engine.options.substructureSearch, undefined, engine.engine.optionsSettings.delimiter, engine.engine.optionsSettings.guiTypeFieldNumber, engine.engine.optionsSettings.parameterNameFieldNumber, engine.engine.optionsSettings.requiredActionDelimiter, engine.engine.optionsSettings.requiredActionTypeFieldNumber, engine.engine.optionsSettings.requiredActionDataFieldNumber);

					//Update status info to the user
					this.dockWidget.widgets.progressBar.value = this.dockWidget.widgets.progressBar.value + 5;
					this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Connecting to {0}...").format(engine.engine.ID);

					//Get molecule data from webservice
					engineResult = engine[engine.engine.molecule.substructureSearchMethod](value, parameters, this.options.spinboxMaximumHits.value, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData, this.lastAtomsNumberStructureSearch, this.lastBondNumberStructureSearch);
					allResponse = engineResult.response;
					allErrorOutput = engineResult.allErrorOutput;

					//Control errors
					if (MoleculeSearcher.controlErrors(allResponse, allErrorOutput, engineResult.exitCode, engine.engine.ID, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData)) {
						return false;
					}

					//Get array of IDs from XML data
					compoundRetrieved = xmlManagement.getElementsFromXML(allResponse, engine.engine.molecule.iDArrayElement);
					this.dockWidget.widgets.progressBar.value = this.dockWidget.widgets.progressBar.value + 5;

					//Getting the data of the molecules received above
					this.molecule.result = engine[engine.engine.molecule.detailMethod](compoundRetrieved, this.dockWidget.widgets.progressBar, this.molecule.result, allResponse, this.options.spinboxMaximumHits.value, this.dockWidget.widgets.labelStatusInfoData);

					//Fill the molecules data grid
					this.fillTable(this.molecule, engine, MoleculeSearcher.moleculeFeatureID, compoundRetrieved.length, this.dockWidget.widgets.progressBar, undefined, this.options.spinboxMaximumHits.value);
					result = true;
				}
			}
		}

		return result;
	};

	MoleculeSearcherViewer.prototype.moleculeSearchManagement = function (aSearchType) {

		var i, engine, flagSearch, flagRightSearch, flagError, warning_txt, message, searchType;

		//We abort if there is an active process still running
		if (this.activeProcess()) {
			return;
		}

		//SearchType: 0 = textSearch; 1 = structure search; 2 = substructure search
		if (aSearchType === undefined || !aSearchType) {
			searchType = 0;
		} else {
			searchType = aSearchType;
		}

		try {
			flagSearch = false;
			flagError = false;
			flagRightSearch = false;

			if (this.availableEngines.length === 0) {
				warning_txt = qsTr("At least one search engine is needed");
				this.dockWidget.widgets.labelStatusInfoData.text = "<font color=red>{0}</font>".format(warning_txt);
				MessageBox.critical(warning_txt);
			} else {

				//Disconnect signal, activate flags and clean widget before results.
				flagSearch = true;
				this.molecule.flagSearch = true;
				this.cleanWidget();
				this.molecule.flagCellChanged = this.secureSignalManagement(this.molecule.tableWidget.cellChanged, this.cellChanged, "disconnect", this.molecule.flagCellChanged, this.molecule);
				this.molecule.flagCurrentCellChanged = this.secureSignalManagement(this.molecule.tableWidget.currentCellChanged, this.moleculeChanged, "disconnect", this.molecule.flagCurrentCellChanged, this);
				this.dockWidget.widgets.tabWidget.setCurrentIndex(0);

				for (i = 0; i < this.availableEngines.length; i++) {
					engine = this.availableEngines[i];
					if (engine.active) {
						switch (searchType) {
						case 0: //Text Search
							if (MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, engine) && this.moleculeTextSearch(engine)) {
								flagRightSearch = true;
							}
							break;
						case 1: // Structure Search
							if (MoleculeSearcher.hasFeature(MoleculeSearcher.structureSearchFeatureID, engine) && this.moleculeStructureSearch(engine)) {
								flagRightSearch = true;
							}
							break;
						case 2: // Substructure Search
							if (MoleculeSearcher.hasFeature(MoleculeSearcher.structureSearchFeatureID, engine) && this.moleculeSubstructureSearch(engine)) {
								flagRightSearch = true;
							}
							break;
						}
					}
				}
			}
		} catch (err) {
			//Check if other engines have performed a successful search
			if (!flagRightSearch) {
				flagError = true;
				this.cleanWidget("<font color=red>{0}</font>".format(qsTr("An exception has occurred during the search with " + engine.id)));
			} else {
				flagError = false;
			}

		} finally {
			//Close progress bar and connect the signal again
			if (!flagError) {
				if (flagSearch) {
					if (this.molecule.result.ID.length > 0) {
						message = "<font color=darkgreen>{0}</font>".format(qsTr("Molecules found {0}").format(this.molecule.result.ID.length));
					} else {
						if (!this.dockWidget.widgets.progressBar.wasCanceled) {
							message = "<font color=red>{0}</font>".format(qsTr("No molecules found"));
						} else {
							message = this.dockWidget.widgets.labelStatusInfoData.text;
						}
						this.cleanWidget();
					}
				} else {
					message = "<font color=red>{0}</font>".format(qsTr("The engine features disallow to search molecules"));
				}

				this.molecule.flagCellChanged = this.secureSignalManagement(this.molecule.tableWidget.cellChanged, this.cellChanged, "connect", this.molecule.flagCellChanged, this.molecule);
				this.molecule.totalRowsChecked = this.updateCheckedRows(this.molecule);
				this.molecule.tableWidget.selectRow(0);
				this.molecule.rowClicked = 0;
				if (this.dockWidget.widgets.progressBar.wasCanceled) {
					this.cleanWidget();
				}
				this.finalizeProgressBar(message);
				this.molecule.flagCurrentCellChanged = this.secureSignalManagement(this.molecule.tableWidget.currentCellChanged, this.moleculeChanged, "connect", this.molecule.flagCurrentCellChanged, this);
			}
			this.molecule.flagSearch = false;
		}
	};

	MoleculeSearcherViewer.prototype.structureSearchManagement = function () {

		this.moleculeSearchManagement(1);
		this.molecule.tbStructureSearch.enabled = false;
		this.molecule.tbSubstructureSearch.enabled = false;
		this.lastMolFileStructureSearchLoaded = "";
		this.lastAtomsNumberStructureSearchLoaded = undefined;
		this.lastBondNumberStructureSearchLoaded = undefined;
	};

	MoleculeSearcherViewer.prototype.substructureSearchManagement = function () {

		this.moleculeSearchManagement(2);
		this.molecule.tbStructureSearch.enabled = false;
		this.molecule.tbSubstructureSearch.enabled = false;
		this.lastMolFileStructureSearchLoaded = "";
		this.lastAtomsNumberStructureSearchLoaded = undefined;
		this.lastBondNumberStructureSearchLoaded = undefined;
	};

	MoleculeSearcherViewer.prototype.moleculeImport = function (aID, aEngine) {

		var engine, engineResult, allResponse, allErrorOutput, auxMolFile, molFile, myManagement, importResult, result, totalMolecules;

		result = false;
		engine = aEngine;
		myManagement = new ManageStructureData();

		if (aEngine.active && MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, aEngine)) {

			//Getting molecule file
			engineResult = engine[engine.engine.molecule.getMethod](aID);
			allResponse = engineResult.response;
			allErrorOutput = engineResult.allErrorOutput;
			this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Importing molecule with ID {0} from {1}").format(this.molecule.result.ID[0], aEngine.engine.ID);

			//Control errors
			if (MoleculeSearcher.controlErrors(allResponse, allErrorOutput, engineResult.exitCode, engine.engine.ID, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData)) {
				return;
			}

			//Get mol file and return the status of the import process
			auxMolFile = myManagement[engine.engine.molecule.downloadManagementMethod](allResponse, engine.engine.molecule.molFileXMLElement);
			molFile = this.parseMolFile(auxMolFile);
			totalMolecules = mainWindow.activeDocument.itemCount("Molecule");
			importResult = molecule.importMolfile(molFile);
			if (importResult === undefined || importResult === "" || importResult.length < 3 || (totalMolecules + 1) !== mainWindow.activeDocument.itemCount("Molecule")) {
				result = false;
			} else {
				result = true;
			}
		}

		return result;
	};

	MoleculeSearcherViewer.prototype.moleculeImportManagement = function (aOnlyFirst) {

		var i, id, items, engine, engineItem, moleculeOK, moleculeKO, flagError, warning_txt, message, lastEngine, flagDownload;

		try {

			//We abort if there is an active process still running
			if (this.activeProcess()) {
				return;
			}

			this.molecule.flagImport = true;
			Application.mainWindow.removeEventHandler("selectionChanged", "Application.moleculeSearcher.onSelectionChanged");
			this.initializeProgressBar();
			moleculeOK = 0;
			moleculeKO = 0;
			flagError = false;

			if (aOnlyFirst !== undefined && aOnlyFirst) {

				if (this.availableEngines.length === 0) {
					warning_txt = qsTr("At least one search engine is needed");
					this.dockWidget.widgets.labelStatusInfoData.text = "<font color=red>{0}</font>".format(warning_txt);
					MessageBox.critical(warning_txt);
				} else {

					if (this.molecule.rowClicked > -1) {
						id = this.molecule.result.ID[this.molecule.rowClicked];
						engineItem = this.molecule.result.Database[this.molecule.rowClicked];
					} else {
						id = this.molecule.result.ID[0];
						engineItem = this.molecule.result.Database[0];
					}

					//Import molecule using the right engine
					engine = MoleculeSearcher.getEngineInstance(engineItem, this.availableEngines);
					if (engine.active && MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, engine)) {
						this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Connecting to {0}...").format(engine.engine.ID);
						if (!this.moleculeImport(id, engine)) {
							moleculeKO++;
						} else {
							moleculeOK++;
							flagDownload = true;
						}
					}
				}
			} else {

				//Loop to get all the selected molecules checked int the table
				items = this.molecule.tableWidget.items;
				for (i = 0; i < items.length && !this.dockWidget.widgets.progressBar.wasCanceled; i++) {
					if (items[i][this.molecule.statusFieldNumber].checkState === 2) {

						//Find the proper engine instance using the ID of the record
						engineItem = items[i][this.molecule.engineFieldNumber].text;
						engine = MoleculeSearcher.getEngineInstance(engineItem, this.availableEngines);
						if (engine.active && MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, engine)) {

							//Update progress bar information about engine only the first time
							if (lastEngine === undefined || lastEngine !== engine.engine.ID) {
								this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Connecting to {0}...").format(engine.engine.ID);
							}

							//Update progress bar
							this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Importing molecule with ID {0} from {1}").format(this.molecule.result.ID[i], engine.engine.ID);

							//We import the molecule to the current document
							if (!this.moleculeImport(this.molecule.result.ID[i], engine)) {
								moleculeKO++;
							} else {
								moleculeOK++;
							}

							//Get last engine and update flag
							lastEngine = engine.engine.ID;
							flagDownload = true;
						}
					}
					MoleculeSearcherViewer.updateProgressBar(this.dockWidget.widgets.progressBar, (i + 1), items.length, 0, 100);
				}
			}

		} catch (e) {
			flagError = true;
			message = "<font color=red>{0}</font>".format(qsTr("An exception has occurred during the import"));
			this.finalizeProgressBar(message);
			MessageBox.critical(e);

		} finally {
			if (!flagError) {
				if (flagDownload) {
					if (moleculeKO > 0) {
						message = '<font color="red">{0}</font>'.format(qsTr("{0} of {1} molecules imported").format(moleculeOK, moleculeKO + moleculeOK));
					} else {
						message = '<font color="darkgreen">{0}</font>'.format(qsTr("{0} of {1} molecules imported").format(moleculeOK, moleculeKO + moleculeOK));
					}
				} else {
					message = '<font color="red">{0}</font>'.format(qsTr("The engine features disallow to import molecule"));
				}
				this.finalizeProgressBar(message);
			}
			Application.mainWindow.installEventHandler("selectionChanged", "Application.moleculeSearcher.onSelectionChanged");
			this.molecule.flagImport = false;
		}
	};

	MoleculeSearcherViewer.prototype.downloadMolecule = function (aID, aEngine, aDownloadDirectory) {

		var engine, engineResult, downloadDirectory, filename, allResponse, allErrorOutput, auxMolFile, molFile, myManagement, result;

		result = false;
		engine = aEngine;
		myManagement = new ManageStructureData();
		downloadDirectory = new Dir(aDownloadDirectory);

		if (aEngine.active && MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, aEngine) && downloadDirectory.exists) {

			//Retrieving data in a mol file format
			engineResult = engine[engine.engine.molecule.downloadMethod](aID);
			allResponse = engineResult.response;
			allErrorOutput = engineResult.allErrorOutput;
			this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Downloading molecule with ID {0} from {1}").format(aID, aEngine.engine.ID);

			//Control errors
			if (MoleculeSearcher.controlErrors(allResponse, allErrorOutput, engineResult.exitCode, engine.engine.ID, this.dockWidget.widgets.progressBar, this.dockWidget.widgets.labelStatusInfoData)) {
				return;
			}

			//Get mol file and return the status of the import process
			auxMolFile = myManagement[engine.engine.molecule.downloadManagementMethod](allResponse, engine.engine.molecule.molFileXMLElement);
			molFile = this.parseMolFile(auxMolFile);
			filename = downloadDirectory.filePath("molecule_" + engine.engine.ID + "_" + aID + ".mol");
			result = MoleculeSearcher.writeToFile(filename, molFile, false);
		}

		return result;
	};

	MoleculeSearcherViewer.prototype.moleculeDownloadManagement = function () {

		var i, outputDirPath, engine, engineItem, lastEngine, flagDownload, flagError, flagCancel, moleculeOK, moleculeKO, items, message, warning_txt;

		try {

			//We abort if there is an active process still running
			if (this.activeProcess()) {
				return;
			}

			//Initialize flags
			this.molecule.flagDownload = true;
			flagDownload = false;
			flagError = false;
			flagCancel = false;

			//Ask for download directory
			if (settings.value(outputDirKey, "") !== "") {
				outputDirPath = FileDialog.getExistingDirectory(settings.value(outputDirKey, ""), qsTr("Molecule Search - Download Directory"));
			} else {
				outputDirPath = FileDialog.getExistingDirectory(Dir.home(), qsTr("Molecule Search - Download Directory"));
			}

			//If the user does not click cancel
			if (outputDirPath !== "") {
				//Check for engines
				if (this.availableEngines.length === 0) {
					warning_txt = qsTr("At least one search engine is needed");
					this.dockWidget.widgets.labelStatusInfoData.text = "<font color=red>{0}</font>".format(warning_txt);
					MessageBox.critical(warning_txt);
				} else {

					//Fill table spectra with the above information
					moleculeOK = 0;
					moleculeKO = 0;
					items = this.molecule.tableWidget.items;

					//Manage Progress Bar
					this.initializeProgressBar();

					//Loop to get all the selected molecules checked in the table
					for (i = 0; i < items.length && !this.dockWidget.widgets.progressBar.wasCanceled; i++) {
						if (items[i][this.molecule.statusFieldNumber].checkState === 2) {

							//Find the proper engine instance using the ID of the record
							engineItem = items[i][this.molecule.engineFieldNumber].text;
							engine = MoleculeSearcher.getEngineInstance(engineItem, this.availableEngines);
							if (engine.active && MoleculeSearcher.hasFeature(MoleculeSearcher.moleculeFeatureID, engine)) {

								//Update progress bar information about engine only the first time or when the engine had changed
								if (lastEngine === undefined || lastEngine !== engine.engine.ID) {
									this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Connecting to {0}...").format(engine.engine.ID);
								}

								//We download the molecule to the specific directory
								if (!this.downloadMolecule(this.molecule.result.ID[i], engine, outputDirPath)) {
									moleculeKO++;
								} else {
									moleculeOK++;
								}

								//Get last engine
								lastEngine = engine.engine.ID;
								flagDownload = true;
							}
						}
						MoleculeSearcherViewer.updateProgressBar(this.dockWidget.widgets.progressBar, (i + 1), items.length, 0, 100);
					}
				}
			} else {
				flagCancel = true;
			}

		} catch (err) {
			flagError = true;
			this.finalizeProgressBar("<font color=red>{0}</font>".format(qsTr("An exception has occurred during the download")));
			this.clearTable(this.dockWidget.widgets.tableWidgetMoleculesResult, this.dockWidget.widgets.progressBar);

		} finally {
			//Save values into the registry
			if (!flagError) {
				if (flagDownload) {
					if (moleculeKO > 0) {
						message = '<font color="red">{0}</font>';
					} else {
						message = '<font color="darkgreen">{0}</font>';
					}
					message = message.format(qsTr("{0} of {1} molfiles downloaded").format(moleculeOK, moleculeKO + moleculeOK));
				} else if (!flagCancel) {
					message = '<font color="red">{0}</font>'.format("The engine features disallow to download molecule");
				}
				settings.setValue(outputDirKey, outputDirPath);
				this.finalizeProgressBar(message);
				this.molecule.flagDownload = false;
			}
		}
	};

	MoleculeSearcherViewer.prototype.importAndDownloadSpectra = function (aID, aExperimentID, aEngine, aDownloadDirectory, aImport, aFilename) {

		var engine, engineResult, downloadDirectory, filename, xmlManagement, result;

		result = false;
		engine = aEngine;
		xmlManagement = new ManageStructureData();
		downloadDirectory = new Dir(aDownloadDirectory);
		if (aEngine.active && MoleculeSearcher.hasFeature(MoleculeSearcher.spectraFeatureID, aEngine) && downloadDirectory.exists) {

			//Update progress bar properly
			if (aImport !== undefined && aImport) {
				this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Importing spectrum with ID {0} from {1}").format(aID, aEngine.engine.ID);
			} else {
				this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Downloading spectrum with ID {0} from {1}").format(aID, aEngine.engine.ID);
			}

			//Retrieving spectrum from webservice
			engineResult = engine[engine.engine.spectra.importAndDownloadMethod](aID, aExperimentID);

			//Check the right encode and write file
			filename = downloadDirectory.filePath(aFilename);
			File.copy(engineResult, filename);
			result = true;

			//Import spectra to current document whether is needed and remove temp file
			if (aImport !== undefined && aImport && result) {
				result = serialization.open(downloadDirectory.filePath(filename));
				File.remove(filename);
			}
		}

		return result;
	};

	MoleculeSearcherViewer.prototype.importAndDownloadSpectraManagement = function (aImport) {

		var i, outputDirPath, engine, engineItem, lastEngine, flagDownload, flagError, flagCancel, spectraOK, spectraKO, items, message, warning_txt, fileName;

		try {

			//We abort if there is an active process still running
			if (this.activeProcess()) {
				return;
			}

			//Control active processes and remove event to avoid problems with serialization.open and onSelectionChanged
			if (aImport !== undefined && aImport) {
				this.spectra.flagImport = true;
			} else {
				this.spectra.flagDownload = true;
			}

			//Initialize flags and remove event
			flagDownload = false;
			flagError = false;
			flagCancel = false;
			Application.mainWindow.removeEventHandler("selectionChanged", "Application.moleculeSearcher.onSelectionChanged");

			//Ask for download directory
			if (aImport === undefined || !aImport) {
				if (settings.value(outputDirKey, "") !== "") {
					outputDirPath = FileDialog.getExistingDirectory(settings.value(outputDirKey, ""), qsTr("Molecule Search - Download Directory"));
				} else {
					outputDirPath = FileDialog.getExistingDirectory(Dir.home(), qsTr("Molecule Search - Download Directory"));
				}
			} else {
				outputDirPath = Dir.temp();
			}

			//If the user does not click cancel
			if (outputDirPath !== "") {
				//Check for engines
				if (this.availableEngines.length === 0) {
					warning_txt = qsTr("At least one search engine is needed");
					this.dockWidget.widgets.labelStatusInfoData.text = "<font color=red>{0}</font>".format(warning_txt);
					MessageBox.critical(warning_txt);
				} else {

					//Fill table spectra with the above information
					spectraOK = 0;
					spectraKO = 0;
					items = this.spectra.tableWidget.items;

					//Manage Progress Bar
					this.initializeProgressBar();

					//Loop to get all the selected spectra checked in the table
					for (i = 0; i < items.length && !this.dockWidget.widgets.progressBar.wasCanceled; i++) {
						if (items[i][this.spectra.statusFieldNumber].checkState === 2) {

							//Find the proper engine instance using the ID of the record
							engineItem = items[i][this.spectra.engineFieldNumber].text;
							engine = MoleculeSearcher.getEngineInstance(engineItem, this.availableEngines);
							if (engine.active && MoleculeSearcher.hasFeature(MoleculeSearcher.spectraFeatureID, engine)) {

								//Update progress bar information about engine only the first time or when the engine had changed
								if (lastEngine === undefined || lastEngine !== engine.engine.ID) {
									this.dockWidget.widgets.labelStatusInfoData.text = qsTr("Connecting to {0}...").format(engine.engine.ID);
								}

								fileName = engine.id === BMRB_Engine.ID ? this.spectra.result.Comments[i] : this.spectra.result.Filename[i];

								//We download the spectra to the specific directory
								if (!this.importAndDownloadSpectra(this.spectra.result.ID[i], fileName, engine, outputDirPath, aImport, this.spectra.result.Filename[i])) {
									spectraKO++;
								} else {
									spectraOK++;
								}

								//Get last engine
								lastEngine = engine.engine.ID;
								flagDownload = true;
							}
						}
						MoleculeSearcherViewer.updateProgressBar(this.dockWidget.widgets.progressBar, (i + 1), items.length, 0, 100);
					}
				}
			} else {
				flagCancel = true;
			}

		} catch (err) {
			flagError = true;
			this.finalizeProgressBar("<font color=red>{0}</font>".format(qsTr("An exception has occurred during the operation of {0}")));
			this.clearTable(this.dockWidget.widgets.tableSpectraResult, this.dockWidget.widgets.progressBar);

		} finally {
			//Save values into the registry
			if (!flagError && !flagCancel) {
				if (flagDownload) {
					if (spectraKO > 0) {
						message = '<font color="red">{0}</font>';
					} else {
						message = '<font color="darkgreen">{0}</font>';
					}
					if (aImport) {
						message = message.format(qsTr("{0} of {1} spectra files imported").format(spectraOK, spectraKO + spectraOK));
					} else {
						message = message.format(qsTr("{0} of {1} spectra files downloaded").format(spectraOK, spectraKO + spectraOK));
					}
				} else {
					message = '<font color="red">{0}</font>';
					if (aImport) {
						message = message.format(qsTr("The engine features disallow to import spectra"));
					} else {
						message = message.format(qsTr("The engine features disallow to download spectra"));
					}
				}
				if (aImport === undefined || !aImport) {
					settings.setValue(outputDirKey, outputDirPath);
				}
				this.finalizeProgressBar(message);
			}
			Application.mainWindow.installEventHandler("selectionChanged", "Application.moleculeSearcher.onSelectionChanged");
			if (aImport !== undefined && aImport) {
				this.spectra.flagImport = false;
			} else {
				this.spectra.flagDownload = false;
			}
		}
	};

	MoleculeSearcherViewer.prototype.onSelectionChanged = function (aJSONString) {

		var value, mol, item, message, compoundTableState, flagSelectedMolecule;

		//We let to structure by molecule only if there is a single molecule selected
		this.molecule.tbActiveLoad.enabled = false;
		if (aJSONString && mainWindow.activeDocument.selection().length === 1) {
			try {
				value = JSON.parse(aJSONString);
				item = mainWindow.activeDocument.selection()[0];
				if (value.Molecule && item.name === "Molecule") {
					mol = new Molecule(item);
					if (item.isValid()) {
						compoundTableState = mainWindow.activeDocument.selectedMolTableItems();
						if (compoundTableState.length < 1) {
							this.loadActiveMoleculeForStructureSearch(mol);
						} else {
							this.clearLoadedStructureSearch();
						}
					}
				}
			} catch (err) {
				message = "<font color=red>{0}</font>".format(qsTr("An exception has occurred on item selection change"));
				this.finalizeProgressBar(message);
			}

		//If users deselect page items and we have not molecules selected but we have a row selected in compound table we get it
		} else {
			flagSelectedMolecule = this.thereIsMoleculePageItemSelected();
			compoundTableState = mainWindow.activeDocument.selectedMolTableItems();
			if (!flagSelectedMolecule) {
				if (compoundTableState.length === 1) {
					this.loadActiveMoleculeForStructureSearch(compoundTableState[0]);
				}
			}
		}
	};

	MoleculeSearcherViewer.prototype.checkAllRows = function () {

		var i, items;

		this.flagCellChanged = this.manageSignal(this.tableWidget.cellChanged, this.slotCellChanged, "disconnect", this.flagCellChanged, this);
		items = this.tableWidget.items;
		for (i = 0; i < items.length; i++) {
			if (items[i][this.statusFieldNumber].checkState !== 2) {
				items[i][this.statusFieldNumber].checkState = 2;
				this.totalRowsChecked++;
			}
		}

		this.tableWidget.items = items;
		this.tbCheckAll.enabled = false;
		this.tbUncheckAll.enabled = true;
		this.tbImport.enabled = true;
		this.tbDownload.enabled = true;
		this.statusBar.text = '<font color="black">{0}</font>'.format(qsTr('{0} checked {1}').format(this.ID, this.totalRowsChecked));
		this.flagCellChanged = this.manageSignal(this.tableWidget.cellChanged, this.slotCellChanged, "connect", this.flagCellChanged, this);
	};

	MoleculeSearcherViewer.prototype.uncheckAllRows = function () {

		var i, items;

		this.flagCellChanged = this.manageSignal(this.tableWidget.cellChanged, this.slotCellChanged, "disconnect", this.flagCellChanged, this);
		items = this.tableWidget.items;
		for (i = 0; i < items.length; i++) {
			if (items[i][this.statusFieldNumber].checkState !== 0) {
				items[i][this.statusFieldNumber].checkState = 0;
				this.totalRowsChecked--;
			}
		}

		this.tableWidget.items = items;
		this.tbCheckAll.enabled = true;
		this.tbUncheckAll.enabled = false;
		this.tbImport.enabled = false;
		this.tbDownload.enabled = false;
		this.statusBar.text = '<font color="black">{0}</font>'.format(qsTr("{0} checked {1}").format(this.ID, this.totalRowsChecked));
		this.flagCellChanged = this.manageSignal(this.tableWidget.cellChanged, this.slotCellChanged, "connect", this.flagCellChanged, this);
	};

	MoleculeSearcherViewer.prototype.textSearch = function (aTextChanged) {

		if (aTextChanged !== "") {
			this.molecule.tbTextSearch.enabled = true;
		} else {
			this.molecule.tbTextSearch.enabled = false;
			this.cleanWidget();
		}
	};

	MoleculeSearcherViewer.prototype.visibilityChanged = function () {

		//Install or uninstall the event
		if (this.dockWidget.visible) {
			Application.mainWindow.installEventHandler("selectionChanged", "Application.moleculeSearcher.onSelectionChanged");
			Application.mainWindow.installEventHandler("MouseButtonPress", "Application.moleculeSearcher.onMouseButtonPress");
			this.molecule.flagTimerCompundsTable = this.secureSignalManagement(this.molecule.timerCompoundsTable.timeout, this.loadCompoundsTableSignal, "connect", this.molecule.flagTimerCompundsTable, this);
			this.loadInitialMolecule();
		} else {
			Application.mainWindow.removeEventHandler("selectionChanged", "Application.moleculeSearcher.onSelectionChanged");
			Application.mainWindow.removeEventHandler("MouseButtonPress", "Application.moleculeSearcher.onMouseButtonPress");
			this.molecule.flagTimerCompundsTable = this.secureSignalManagement(this.molecule.timerCompoundsTable.timeout, this.loadCompoundsTableSignal, "disconnect", this.molecule.flagTimerCompundsTable, this);
		}
	};

	MoleculeSearcherViewer.prototype.selectionChangesOnCompoundsTable = function () {

		var compoundTableState, message, flagMolelculePageSelected, onlyOneMolPageItemSelected;

		try {

			//Get the current selection of the compounds table
			compoundTableState = mainWindow.activeDocument.selectedMolTableItems();

			//Check if we have a molecule selected in the current document
			flagMolelculePageSelected = this.thereIsMoleculePageItemSelected();

			//If we have both a mole page item selected and a item in compound table selected we disable the search structure feature
			if ((flagMolelculePageSelected && compoundTableState.length > 0) || (compoundTableState.length > 1)) {
				this.clearLoadedStructureSearch();

			//If not we enable the search structure feature to the current selection of the compound table
			} else if (!flagMolelculePageSelected && compoundTableState.length === 1) {
				this.loadActiveMoleculeForStructureSearch();

			//If user deselects all rows in compounf table but there is only a molecule page item selected then we use it
			} else if (compoundTableState.length === 0) {
				onlyOneMolPageItemSelected = this.thereIsMoleculePageItemSelected(true);
				if (onlyOneMolPageItemSelected) {
					this.loadActiveMoleculeForStructureSearch();
				} else {
					//We use this flag which only is activated when users clickes the blue arrow button using the compound table selection
					if (this.deselectedByClickingLoadActiveMolecule) {
						this.deselectedByClickingLoadActiveMolecule = false;
					} else {
						this.clearLoadedStructureSearch();
					}
				}
			}

		} catch (err) {
			message = "<font color=red>{0}</font>".format(qsTr("An exception has occurred on item selection change on compounds table"));
			this.molecule.statusBar(message);
		}
	};

	MoleculeSearcherViewer.prototype.loadCompoundsTableSignal = function () {

		this.molecule.compoundsTable = mainWindow.getObject("CompoundTable");

		//Check if the user is running the compounds table
		if (this.molecule.compoundsTable !== null) {

			//Manages selections changes on compounds table
			this.molecule.flagCompoundsTable = this.secureSignalManagement(this.molecule.compoundsTable.widgets.tableView.itemSelectionChanged, this.selectionChangesOnCompoundsTable, "connect", this.molecule.flagCompoundsTable, this);
		} else {
			this.molecule.flagCompoundsTable = false;
		}
	};

	MoleculeSearcherViewer.prototype.pressingKeyTextSearch = function () {

		if (this.molecule.leTextInput.text !== "") {
			this.moleculeSearchManagement();
		}
	};

	MoleculeSearcherViewer.prototype.onMouseButtonPress = function () {

		this.molecule.tbActiveLoad.enabled = false;
	};

	MoleculeSearcherViewer.prototype.cancelCurrentProcess = function () {

		this.dockWidget.widgets.progressBar.wasCanceled = true;
	};

	//***********************MAIN PROGRAM**********************
	this.checkEngines();
	this.cleanWidget();
	this.guiAdaptionToFeatures();
	this.synchronizeOptionsObjectWithGUI(this.optionsDialog);
	this.loadSettingsOptions(this.optionsDialog);
	this.loadInitialMolecule();

	//*********MOLECULES SIGNAL***********
	//Prepare the molecule object to operate in reduced this context
	this.molecule.slotCellChanged = this.cellChanged;
	this.molecule.manageSignal = this.secureSignalManagement;
	this.molecule.updateRows = this.updateCheckedRows;

	//Search pressing enter
	this.molecule.leTextInput.returnPressed.connect(this, this.pressingKeyTextSearch);

	//Search button
	this.molecule.tbTextSearch.clicked.connect(this, this.moleculeSearchManagement);

	//Enable button when the user inserts a text.
	this.molecule.leTextInput.textChanged.connect(this, this.textSearch);

	//Select molecule from table for displaying image and showing associated spectra information
	this.molecule.flagCurrentCellChanged = this.secureSignalManagement(this.molecule.tableWidget.currentCellChanged, this.moleculeChanged, "connect", this.molecule.flagCurrentCellChanged, this);

	//Control the number of checked molecules
	this.molecule.flagCellChanged = this.secureSignalManagement(this.molecule.tableWidget.cellChanged, this.cellChanged, "connect", this.molecule.flagCellChanged, this.molecule);

	//Manage Check all Button Signal
	this.molecule.tbCheckAll.clicked.connect(this.molecule, this.checkAllRows);

	//Manage Uncheck all Button Signal
	this.molecule.tbUncheckAll.clicked.connect(this.molecule, this.uncheckAllRows);

	//Open all the selected molecules in Mnova
	this.molecule.tbImport.clicked.connect(this, this.moleculeImportManagement);

	//Download all the selected molecules
	this.molecule.tbDownload.clicked.connect(this, this.moleculeDownloadManagement);

	//Select molecule from table for displaying image and showing associated spectra information
	this.molecule.flagCellDoubleClicked = this.secureSignalManagement(this.molecule.tableWidget.cellDoubleClicked, this.cellDoubleClicked, "connect", this.molecule.flagCellDoubleClicked, this);

	//Load structure molecule into label
	this.molecule.tbActiveLoad.clicked.connect(this, this.loadActiveMolecule);

	//Search structure molecules
	this.molecule.tbStructureSearch.clicked.connect(this, this.structureSearchManagement);

	//Search substructure molecules
	this.molecule.tbSubstructureSearch.clicked.connect(this, this.substructureSearchManagement);

	//Import the first molecule into Mnova document
	this.molecule.tbActiveResultImport.clicked.connect(this, function () { this.moleculeImportManagement(true); });

	//Timer to manage if the compounds table is opened
	this.molecule.flagTimerCompundsTable = this.secureSignalManagement(this.molecule.timerCompoundsTable.timeout, this.loadCompoundsTableSignal, "connect", this.molecule.flagTimerCompundsTable, this);

	//*********SPECTRA SIGNALS***********
	//Prepare the spectra object to operate in reduced this context
	this.spectra.slotCellChanged = this.cellChanged;
	this.spectra.manageSignal = this.secureSignalManagement;
	this.spectra.updateRows = this.updateCheckedRows;

	//Control the number of checked spectra
	this.spectra.flagCellChanged = this.secureSignalManagement(this.spectra.tableWidget.cellChanged, this.cellChanged, "connect", this.spectra.flagCellChanged, this.spectra);

	//Check all Spectra
	this.spectra.tbCheckAll.clicked.connect(this.spectra, this.checkAllRows);

	//Uncheck all Spectra
	this.spectra.tbUncheckAll.clicked.connect(this.spectra, this.uncheckAllRows);

	//Import selected spectra
	this.spectra.tbImport.clicked.connect(this, function () { this.importAndDownloadSpectraManagement(true); });

	//Download selected spectra
	this.spectra.tbDownload.clicked.connect(this, this.importAndDownloadSpectraManagement);

	//*********MISCELANEUS SIGNALS***********
	//Control selection change
	this.dockWidget.visibilityChanged.connect(this, this.visibilityChanged);

	//Lets cancel to the user
	this.dockWidget.widgets.toolButtonCancel.clicked.connect(this, this.cancelCurrentProcess);

	//Lets shutdown application to the user
	this.dockWidget.widgets.toolButtonExit.clicked.connect(this, this.closeApplication);

	//Lets enable / disable eMolecules engine
	this.dockWidget.widgets.toolButtoneMolecules.clicked.connect(this, function () { this.engineActivationManagement(Emolecules_Engine.ID); });

	//Lets enable / disable BMRB engine
	this.dockWidget.widgets.toolButtonBMRB.clicked.connect(this, function () { this.engineActivationManagement(BMRB_Engine.ID); });

	//Options management
	this.dockWidget.widgets.toolButtonOptions.clicked.connect(this, this.optionsManagement);
}

//***********Static Methods*************
MoleculeSearcherViewer.appendDataToArray = function (aMainArray, aAppendingArray, aInsertAtEnd) {
	"use strict";

	var i;

	if (aInsertAtEnd) {
		for (i = 0; i < aAppendingArray.length; i++) {
			aMainArray.push(aAppendingArray[i]);
		}
	} else {
		for (i = 0; i < aAppendingArray.length; i++) {
			aMainArray.unshift(aAppendingArray[i]);
		}
	}
};

MoleculeSearcherViewer.updateProgressBar = function (aProgressBar, aCurrentValue, aTotalValues, aStartValue, aEndValue) {
	"use strict";

	aProgressBar.value = Math.round(aStartValue + ((aCurrentValue / aTotalValues) * (aEndValue - aStartValue)));
	aProgressBar.update();
};

MoleculeSearcherViewer.setDetailFromIdArray = function (aProgressBar, aValues, responseArray, aEngine, aFeature, aSetOnlyEngine) {
	"use strict";

	var i, valueProperties, mappedFieldProperties, found, dataManagement, dataFromXML, mappedFields, maxEntries;//, neededEntries;

	maxEntries = 0;

	for (i = 0; i < responseArray.length; i++) {
		for (valueProperties in aValues) {
			if (aValues.hasOwnProperty(valueProperties)) {
				if (aProgressBar.wasCanceled) {
					break;
				} else if (valueProperties === "Database") {
					aValues.Database.push(aEngine.engine.ID);
					continue;
				} else {
					found = false;
					for (mappedFieldProperties in aEngine.engine[aFeature][mappedFields]) {
						if (aEngine.engine[aFeature][mappedFields].hasOwnProperty(mappedFieldProperties)) {
							if (aProgressBar.wasCanceled) {
								break;
							} else if (mappedFieldProperties === valueProperties) {
								found = true;
								break;
							}
						}
					}

					if (aSetOnlyEngine !== undefined && aSetOnlyEngine) {
						if (valueProperties === "Database") {
							aValues.Database.unshift(aEngine.engine.ID);
						}
					} else {
						if (found) {
							dataFromXML = dataManagement.getElementsFromXML(responseArray, aEngine.engine[aFeature][mappedFields][mappedFieldProperties]);

							//Get the encrypted ID used in the eMolecules engine
							if (aEngine.engine.ID === Emolecules_Engine.ID) {
								if (valueProperties === "ENC_ID") {
									aEngine.getEncryptedID(dataFromXML);
								}
							}

							MoleculeSearcherViewer.appendDataToArray(aValues[valueProperties], dataFromXML, false);
							if (aFeature === MoleculeSearcher.spectraFeatureID) {
								if (maxEntries < dataFromXML.length) {
									maxEntries = dataFromXML.length;
								}
							}
						}
					}

					if (!found) {
						aValues[valueProperties].unshift(qsTr("N/A"));
					}
				}
			}
		}
	}
};

MoleculeSearcherViewer.setDetailFromStructureData = function (aProgressBar, aValues, aXMLResponse, aEngine, aFeature, aSetOnlyEngine) {
	"use strict";

	var i, valueProperties, mappedFieldProperties, found, dataManagement, dataFromXML, mappedFields, maxEntries, neededEntries;

	//Add data to array using the attributes
	maxEntries = 0;
	mappedFields = "mappedFields";
	dataManagement = new ManageStructureData();
	for (valueProperties in aValues) {
		if (aValues.hasOwnProperty(valueProperties)) {
			if (aProgressBar.wasCanceled) {
				break;
			} else if (valueProperties === "Database") {
				aValues.Database.unshift(aEngine.engine.ID);
				continue;
			} else {
				found = false;
				for (mappedFieldProperties in aEngine.engine[aFeature][mappedFields]) {
					if (aEngine.engine[aFeature][mappedFields].hasOwnProperty(mappedFieldProperties)) {
						if (aProgressBar.wasCanceled) {
							break;
						} else if (mappedFieldProperties === valueProperties) {
							found = true;
							break;
						}
					}
				}

				if (aSetOnlyEngine !== undefined && aSetOnlyEngine) {
					if (valueProperties === "Database") {
						aValues.Database.unshift(aEngine.engine.ID);
					}
				} else {
					if (found) {
						dataFromXML = dataManagement.getElementsFromXML(aXMLResponse, aEngine.engine[aFeature][mappedFields][mappedFieldProperties]);

						//Get the encrypted ID used in the eMolecules engine
						if (aEngine.engine.ID === Emolecules_Engine.ID) {
							if (valueProperties === "ENC_ID") {
								aEngine.getEncryptedID(dataFromXML);
							}
						}

						MoleculeSearcherViewer.appendDataToArray(aValues[valueProperties], dataFromXML, false);
						if (aFeature === MoleculeSearcher.spectraFeatureID) {
							if (maxEntries < dataFromXML.length) {
								maxEntries = dataFromXML.length;
							}
						}
					}
				}
				if (!found) {
					aValues[valueProperties].unshift(qsTr("N/A"));
				}
			}
		}
	}

	//Given that the search engine values are not sent in XML data, we need to complete them
	if (aFeature === MoleculeSearcher.spectraFeatureID) {
		neededEntries = maxEntries - aValues.Database.length;
		for (i = 0; i < neededEntries; i++) {
			aValues.Database.unshift(aEngine.engine.ID);
		}
	}
};

function MoleculeSearcher() {
	'use strict';

	//Instance the script by using the Application global variable
	try {
		if (Application.moleculeSearcher) {
			Application.moleculeSearcher.dockWidget.show();
		} else {
			Application.moleculeSearcher = new MoleculeSearcherViewer();
			Application.mainWindow.installEventHandler("selectionChanged", "Application.moleculeSearcher.onSelectionChanged");
			Application.mainWindow.installEventHandler("MouseButtonPress", "Application.moleculeSearcher.onMouseButtonPress");
			mainWindow.processEvents();
		}

	} catch (Err) {
		MessageBox.critical(Err);
		Application.moleculeSearcher = undefined;
	}
}

//Initialize the var to store the available engines
Env.MoleculeSearcher_Engines = [];

//***********Static Attributes*************
MoleculeSearcher.engineIDs = Env.MoleculeSearcher_Engines;
MoleculeSearcher.moleculeFeatureID = "molecule";
MoleculeSearcher.spectraFeatureID = "spectra";
MoleculeSearcher.structureSearchFeatureID = "structureSearch";
MoleculeSearcher.comboboxGUITypeID = "combobox";
MoleculeSearcher.checkboxGUITypeID = "checkbox";
MoleculeSearcher.spinboxGUITypeID = "spinbox";

//***********Static Methods*************
MoleculeSearcher.getEngineInstance = function (aEngineID, aEngines) {
	"use strict";

	var i, engine;

	if (aEngines === undefined || aEngines.length === 0) {
		switch (aEngineID) {
		case Emolecules_Engine.ID:
			engine = new Emolecules_Engine(Emolecules_Engine.user, Emolecules_Engine.clientKey);
			break;
		case BMRB_Engine.ID:
			engine = new BMRB_Engine();
			break;
		default:
			break;
		}

	} else {
		for (i = 0; i < aEngines.length; i++) {
			engine = aEngines[i];
			if (engine.id === aEngineID || engine.engine.ID === aEngineID) {
				engine = aEngines[i];
				break;
			} else {
				engine = undefined;
			}
		}
	}

	return engine;
};

MoleculeSearcher.getAvailableEngines = function (aAvailableEngines) {
	"use strict";

	var i, engine, engines, engineID;

	engines = [];
	for (i = 0; i < aAvailableEngines.length; i++) {
		engineID = aAvailableEngines[i];
		engine = MoleculeSearcher.getEngineInstance(engineID);
		if (engine !== undefined) {
			engines.push(engine);
		}
	}

	return engines;
};

MoleculeSearcher.hasFeature = function (aFeature, aEngine) {
	"use strict";

	var i, engine, feature, found;

	engine = aEngine;
	found = false;
	for (i = 0; i < engine.features.length && !found; i++) {
		feature = engine.features[i];
		if (feature === aFeature) {
			found = true;
		}
	}

	return found;
};

MoleculeSearcher.writeToFile = function (aFilePath, aData, aPDF) {
	"use strict";

	var file, resultFile, result;

	//Write string into a file
	result = false;
	file = new File(aFilePath);
	if (File.exists(aFilePath)) {
		File.remove(aFilePath);
	}
	file.open(File.WriteOnly);
	resultFile = new BinaryStream(file);
	if (aPDF === undefined || (aPDF !== undefined && !aPDF)) {
		resultFile.codec = "UTF-8";
	}
	resultFile.writeBytes(aData);
	file.close();
	if (File.exists(aFilePath)) {
		result = true;
	}

	return result;
};

MoleculeSearcher.throwError = function (aMessage, aStatusInfoBar, aProgressBar, aKeepCancelFlag) {
	"use strict";

	var wasCanceled;

	wasCanceled = aProgressBar.wasCanceled;
	aStatusInfoBar.text = "<font color=Red>" + aMessage + "</font>";
	MessageBox.critical(aMessage);
	if (aKeepCancelFlag !== undefined && aKeepCancelFlag) {
		if (wasCanceled) {
			aProgressBar.wasCanceled = wasCanceled;
		}
	}

	return true;
};

MoleculeSearcher.controlErrors = function (aResponse, aAllErrorOutput, aExitCode, aEngineID, aProgressBar, aStatusInfoBar, aExitCodeSilenceFeature) {
	"use strict";

	var i, message, flagError, engine;

	flagError = false;

	if (aExitCodeSilenceFeature !== undefined && aExitCodeSilenceFeature === true) {
		engine = MoleculeSearcher.getEngineInstance(aEngineID, this.availableEngines);
		if (engine.engine !== undefined) {
			if (engine.engine.exitCodesToBeSilenced !== undefined) {
				if (typeof (engine.engine.exitCodesToBeSilenced) === "object") {
					if (Array.isArray(engine.engine.exitCodesToBeSilenced)) {
						if (engine.engine.exitCodesToBeSilenced.length > 0) {
							for (i = 0; i < engine.engine.exitCodesToBeSilenced.length; i++) {
								if (aExitCode === engine.engine.exitCodesToBeSilenced[i]) {
									flagError = true;
									return flagError;
								}
							}
						}
					}
				}
			}
		}
	}

	if (aExitCode !== undefined && aExitCode === -1) {
		message = qsTr("Request failed on {0} side").format(aEngineID);
		flagError = MoleculeSearcher.throwError(message, aProgressBar, aStatusInfoBar);
		return flagError;
	}

	if (aExitCode !== undefined && aExitCode === -2) {
		message = qsTr("Request timeout in {0} database").format(aEngineID);
		flagError = MoleculeSearcher.throwError(message, aProgressBar, aStatusInfoBar);
		return flagError;
	}

	if (aExitCode !== undefined && aExitCode === -3) {
		message = qsTr("Request aborted by user in {0} database").format(aEngineID);
		flagError = MoleculeSearcher.throwError(message, aProgressBar, aStatusInfoBar, this.dockWidget.widgets.progressBar.wasCanceled);
		return flagError;
	}

	if (aExitCode !== undefined && aExitCode === 401) {
		message = qsTr("Internal server error in {0} database").format(aEngineID);
		if (aExitCode && !isNaN(aExitCode)) {
			message += qsTr("\n\tError code: ") + aExitCode;
		}
		if (aAllErrorOutput) {
			message += qsTr("\n\tDetails: ") + aAllErrorOutput;
		}
		message += qsTr("\nPlease try again later. If the problem persists then contact {0} or Mestrelab tech support").format(aEngineID);
		flagError = MoleculeSearcher.throwError(message, aProgressBar, aStatusInfoBar);
		return flagError;
	}

	if (aExitCode !== undefined && aExitCode !== 0) {
		message = qsTr("Unable to connect to {0} database").format(aEngineID);
		if (aExitCode && !isNaN(aExitCode)) {
			message += qsTr("\n\tError code: ") + aExitCode;
		}
		if (aAllErrorOutput) {
			message += qsTr("\n\tDetails: ") + aAllErrorOutput;
		}
		//Check for a possible internet connection issue
		if (aExitCode && !isNaN(aExitCode) && (aExitCode === 3 || aExitCode === 99)) {
			message += qsTr("\nIf your internet connection is working properly, please contact {0} or Mestrelab tech support").format(aEngineID);
		} else {
			message += qsTr("\nPlease contact {0} or Mestrelab tech support").format(aEngineID);
		}
		flagError = MoleculeSearcher.throwError(message, aProgressBar, aStatusInfoBar);
		return flagError;
	}

	if (aResponse === undefined) {
		message = qsTr("No data available from  {0} database").format(aEngineID);
		flagError = MoleculeSearcher.throwError(message, aProgressBar, aStatusInfoBar);
		return flagError;
	}

	if (aAllErrorOutput !== undefined && aAllErrorOutput !== "") {
		message = qsTr("Error retrieving data from {0}").format(aEngineID);
		if (aExitCode && !isNaN(aExitCode)) {
			message += qsTr("\n\tError code: ") + aExitCode;
		}
		if (aAllErrorOutput) {
			message += qsTr("\n\tDetails: ") + aAllErrorOutput;
		}
		message += qsTr("\nPlease contact {0} or Mestrelab tech support").format(aEngineID);
		flagError = MoleculeSearcher.throwError(message, aProgressBar, aStatusInfoBar);
		return flagError;
	}

	return flagError;
};


//***********Menu Entry*************
if (this.MnUi && this.MnUi.scripts_common && molecule !== undefined) {

	MnUi.scripts_common.scripts_common_MoleculeSearcher = function () {
		'use strict';

		var moleculeSearcher;

		try {
			moleculeSearcher = new MoleculeSearcher();
		} catch (e) {
			MessageBox.critical(e);
		}
	};
}
